[RUBY] RSpec-Testing'users validation'

RSpec-Results of reviewing the'users validation'test code

Review by @jnchito! If you like, please follow the link above!

Background

Nice to meet you. I'm new to Qiita posts, rails and rspec. Please be kind to me. While studying at railstutorial, I am writing the'model spec'of ʻapp / models / user.rb`. I wanted to make it a cool and beautiful test code, so I read articles on how to write rspec for various people. I wrote the spec while devising my own way, and while I thought this way of writing "Isn't it pretty cool?" When I look back at my code while referring to the article, I feel something subtle, I got lost asking "what is the test code that follows the rules?" I can't commit easily and I'm wandering around hell. If you like, I would appreciate it if you could review and point out the test code.

reference

For the test code, I mainly referred to the following by jnchito.

-Youtube video -The first half of everydayrailsrspec -Introduction to RSpec that can be used, part 2 "Mastering frequently used matchers"

I will take a look at the above and write the code in my own way. I didn't refer to the following, but the article that I saw and found my code to be garbage.

-Basic Rule for writing RSpec beautifully

Nested image

--describe: ʻattribute: name is when #create (action) of ʻUser (model) --context: name should be present The name should exist. --it: Verify the pattern of valid or ʻinvalid`.

In the test creation, the image of nesting (?) model > action > attribute > validates: presence:true > valid or invalid I wrote the spec with the grouping in mind. (As another image, it seems that the execution result of $ bundle exec rspec at the bottom of the article looks beautiful.)

Why you can't get out of hell

--Correct use of describe, context, and ʻit. I intended to write it in an easy-to-understand manner in consideration of the case where other people read it. Take a look at [Basic Rule for Writing RSpec Beautifully](https://qiita.com/geshi/items/556179cc6a277efb4f99) "This is no good." (Is there a rule in the first place? That may be a problem because I am aware that I will create and create some rules myself.) --My test code from a professional perspective The test code I wrote is green and I think it's okay, so I want to know "I will do this!" ――Is it not necessary to carry out "what can be saved or what cannot be saved" in each test case? Basically as ʻuser.valid?, To ensure that it failed in the target test, I'm checking the contents of the error (ʻerrors [: name]) as ʻinclude ("can't be blank") , but After that, as ʻuser.save!, (!I feel that this is an exception, but ...) I'm wondering if I should check the following: --The case ofvalid can be saved --The case of ʻinvalid cannot be saved

Below is an excerpt of the code that confirms "what you can or cannot save".

#Invalid if name does not exist
it 'is invalid because the name value does not exists' do
  user.name = nil
  user.valid?
  expect(user.errors[:name]).to include("can't be blank")

  expect(user.save).to be_falsey # ...Confirm that this one line cannot be saved

end

Test code

The content of the test is the validation of common ʻusers.name, ʻuser.email. railstutorial It becomes here.

Well then, immediately, but ...

The following is the current validation.

app/models/user.rb


class User < ApplicationRecord
  before_save :downcase_email

  validates :name,
            presence: true,
            length: { maximum: 50 }
  VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
  validates :email,
            presence: true,
            length: { maximum: 255 },
            format: { with: VALID_EMAIL_REGEX },
            uniqueness: { case_sensitive: false }

  private

    def downcase_email
      self.email.downcase!
    end
end

The following is the ʻuser defined in FactoryBot`.

spec/factories/users.rb


FactoryBot.define do
  factory :user do
    name { "hoge" }
    email { "[email protected]" }
  end
end

Below is the actual test code.

spec/models/user_spec.rb


require 'rails_helper'
require 'pry'

RSpec.describe User, type: :model do
  let(:user) { FactoryBot.build(:user) }

  describe '#create' do
    describe 'attribute: name' do
      context 'name should be present' do
        #If name exists, it must be valid
        it 'is valid because the name has the correct value' do
          expect(user).to be_valid
          expect(user.save).to be_truthy
        end
        #Invalid if name does not exist
        it 'is invalid because the name value does not exists' do
          user.name = nil
          user.valid?
          expect(user.errors[:name]).to include("can't be blank")
          expect(user.save).to be_falsey
        end
      end

      context 'name should be length is 50 characters or less' do
        #Must be valid if name is 50 characters or less
        it 'is valid because the name length is 50 characters or less' do
          user.name = 'a' * 50
          user.valid?
          expect(user.errors[:name]).not_to include('is too long (maximum is 50 characters)')
          expect(user.save).to be_truthy
        end
        #Invalid if name exceeds 50 characters
        it 'is invalid because the name length is 50 characters over' do
          user.name = 'a' * 51
          user.valid?
          expect(user.errors[:name]).to include('is too long (maximum is 50 characters)')
          expect(user.save).to be_falsey
        end
      end

      context 'name should be not blank' do
        #If name is blank, it is invalid
        it 'is invalid because the name is blank' do
          user.name = ' '
          user.valid?
          expect(user.errors[:name]).to include("can't be blank")
          expect(user.save).to be_falsey
        end
      end
    end

    context 'attribute: email' do
      context 'email should be present' do
        #Invalid if email does not exist
        it 'is invalid because the email value does not exists' do
          user.email = nil
          user.valid?
          expect(user.errors[:email]).to include("can't be blank")
          expect(user.save).to be_falsey
        end
      end

      context 'email should be length is 50 characters or less' do
        #If the email is 255 characters or less, it must be valid
        it 'is valid because the email length is 255 characters or less' do
          user.email = 'a' * 243 + '@example.com'
          user.valid?
          expect(user.errors[:email]).not_to include('is too long (maximum is 255 characters)')
          expect(user.save).to be_truthy
        end
        #If the email exceeds 255 characters, it is invalid
        it 'is invalid because the email length is 255 characters over' do
          user.email = 'a' * 244 + '@example.com'
          user.valid?
          expect(user.errors[:email]).to include('is too long (maximum is 255 characters)')
          expect(user.save).to be_falsey
        end
      end

      context 'email should be correct format' do
        #If the email format is correct, it is valid
        it 'is valid because the email is the correct format' do
          user.email = '[email protected]'
          expect(user).to be_valid
          expect(user.save).to be_truthy

          user.email = '[email protected]'
          expect(user).to be_valid
          expect(user.save).to be_truthy

          user.email = '[email protected]'
          expect(user).to be_valid
          expect(user.save).to be_truthy

          user.email = '[email protected]'
          expect(user).to be_valid
          expect(user.save).to be_truthy

          user.email = '[email protected]'
          expect(user).to be_valid
          expect(user.save).to be_truthy
        end
        #Invalid if the email format is incorrect
        it 'is invalid because the email format is incorrect' do
          user.email = 'user@example,com'
          expect(user).not_to be_valid
          expect(user.save).to be_falsey

          user.email = 'user_at_foo.org'
          expect(user).not_to be_valid
          expect(user.save).to be_falsey

          user.email = 'user.name@example.'
          expect(user).not_to be_valid
          expect(user.save).to be_falsey

          user.email = 'foo@bar_baz.com'
          expect(user).not_to be_valid
          expect(user.save).to be_falsey

          user.email = 'foo@bar+baz.com'
          expect(user).not_to be_valid
          expect(user.save).to be_falsey
        end
      end

      context 'email should be unique' do
        #If the same email is already registered, it must be invalid
        it 'is Invalid because the same email already exists' do
          dup_user = user.dup
          dup_user.email = dup_user.email.upcase
          dup_user.save!
          user.valid?
          expect(user.errors[:email]).to include('has already been taken')
          expect(user.save).to be_falsey
        end
      end

      #The email is saved in lowercase
      context 'email should be saved in lowercase' do
        it 'is valid because the email saved in lowercase' do
          user.email = '[email protected]'
          user.save!
          expect(user.reload.email).to eq '[email protected]'
        end
      end

      context 'email should be not blank' do
        #If email is blank, it is invalid
        it 'is invalid because the email is blank' do
          user.email = ' '
          user.valid?
          expect(user.errors[:email]).to include("can't be blank")
          expect(user.save).to be_falsey
        end
      end
    end

  end
end

In the above test, the output of executing $ bundle exec rspec (green) is as follows. The proper use of the test code describe, context, and ʻit` seems to be nested so that the following output will be easier to see.

User
  #create
    attribute: name
      name should be present
        is valid because the name has the correct value
        is invalid because the name value does not exists
      name should be length is 50 characters or less
        is valid because the name length is 50 characters or less
        is invalid because the name length is 50 characters over
      name should be not blank
        is invalid because the name is blank
    attribute: email
      email should be present
        is invalid because the email value does not exists
      email should be length is 50 characters or less
        is valid because the email length is 255 characters or less
        is invalid because the email length is 255 characters over
      email should be correct format
        is valid because the email is the correct format
        is invalid because the email format is incorrect
      email should be unique
        is Invalid because the same email already exists
      email should be saved in lowercase
        is valid because the email saved in lowercase
      email should be not blank
        is invalid because the email is blank

Thank you for your cooperation.

Recommended Posts

RSpec-Testing'users validation'
Customization of validation
Use ControlsFX / Validation
[rails] Set validation
[Rails5] Rspec -validation-