Review by @jnchito! If you like, please follow the link above!
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.
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
--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.)
--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 of
valid 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
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.