This time I will summarize how to write test code using RSpec in order to deepen my understanding of test code. When I wrote the test code for the first time, I remember writing it by imitating it without knowing why I needed the test code, so I would like to return to the beginning and write it in as much detail as possible. By the way, detailed explanation of terms is omitted. Please see the article that is summarized in an easy-to-understand manner. Recommended => Introduction to RSpec that can be used, part 1 "Understanding the basic syntax and useful functions of RSpec"
This is to guarantee the quality by leaving the operation check of the application to the computer. If a person manually checks the operation, omission may occur. You can eliminate human error by automating the test. Even if there are other changes in the specifications, you only have to check the changes, so you can save the trouble of restarting all the tests from 1.
RSpec is a gem used to write test code for Ruby on Rails. Ruby on Rails has a function to test by standard called Minitest, but in the actual development site using Rails, it seems that testing using RSpec is the mainstream, so I use it as a practice.
This time, there are three models of User, Room, and Message in our own chat application, and we will write the flow of the test code for each User model. The purpose of the unit test code of the User model is to check whether the user registration is correct and whether the set validation is working.
・ MacOS Catalina 10.15.6 ・ Ruby 2.6.5 ・ Rails 6.0.3 ・ Gem FactoryBot ・ Gem RSpec ・ Gem Faker
First, we will introduce FactoryBot, RSpec, and Faker.
Gemfile
(Abbreviation)
group :development, :test do
#This time we will use the latest version, so we do not specify the version
gem 'rspec-rails'
#FactoryBot is used to automatically create an instance. You don't have to, but this is also a practice.
gem 'factory_bot_rails'
#Faker will randomly generate a name and password for you. This is also convenient, so let's get used to it.
gem 'faker'
end
(Abbreviation)
Perform bundle installation
% bundle install
Let's create the files needed for RSpec.
% rails g rspec:install
I think you have created some files.
create .rspec
create spec
create spec/spec_helper.rb
create spec/rails_helper.rb
Write the following in your .rspec file
.rspec
--require spec_helper
--format documentation
With this description, you can see the result of the test code on the terminal. Execute the following command to create a file for testing the required User model. This time, only the User model is used, but the procedure is the same for other models.
% rails g rspec:model user
You should now have the files you need.
create spec/models/user_spec.rb
invoke factory_bot
create spec/factories/users.rb
If the users.rb file is not created, create the factories directory in the spec directory and create the users.rb file in it.
We will set up FactoryBot. Write the following in spec / factories / users.rb.
spec/factories/users.rb
#A description for using FactoryBot
FactoryBot.define do
#User class by writing as follows(User model)It will automatically judge that
factory :user do
#Randomly generate the content to be saved in name
name {Faker::Name.first_name}
#Automatically generate email
email {Faker::Internet.email}
#For password, you have to enter the same value twice, including for confirmation, so randomly generate a value as a variable.
#The devise password must be at least 6 characters, so min_Set length to 6 characters or more
password = Faker::Internet.password(min_length: 6)
#Set the password and the variable set above in the password confirmation frame.
password {password}
password_confirmation {password}
end
end
This time, only name, email and password are available, but if there are other items for which validation is set, please add them. This completes the Factory Bot settings. Let's start the console in the terminal and check it.
% rails c
[1] pry(main)> FactoryBot.build(:user)
=> #<User id: nil, name: "颯", email: "[email protected]", created_at: nil, updated_at: nil>
If it is output like this, it's OK! (Name and email are randomly generated by Faker, so they change every time.)
Now let's actually write the test code. First, check the user_spec.rb file.
spec/models/spec_user.rb
require 'rails_helper'
RSpec.describe User, type: :model do
- pending "add some examples to (or delete) #{__FILE__}"
+ describe '#create' do
+ before do
+ @user = FactoryBot.build(:user)
+ end
+ end
end
First of all, I will write like this. Since describe means to explain, it describes what the test is about. Create an instance of user based on the contents set in the spec / factories / users.rb file in the block after before and make it a variable. Since it will be used in a different scope after this, it will be an instance variable.
From here I will write a brief explanation in the code
spec/models/spec_user.rb
RSpec.describe User, type: :model do
#Create a new user(Registration)Because it is a test to do#I am creating
describe '#create' do
# spec/factories/user.Make the contents of rb a calling variable
#Not a local variable because the scope is different@To make it an instance variable
before do
@user = FactoryBot.build(:user)
end
#Describe the condition / situation in context
context 'If you can register as a user' do
#Describe the item to be tested in it
it 'name, email, password, password_If confirmation is entered correctly, you can register a new one.' do
#If the user information created by FactoryBot is correct, it will not be validated and be_pass the test with valid
expect(@user).to be_valid
end
end
end
end
RSpec test code syntax often puts a condition after the context. Think of the content of this context as the if (true) part of the if ~ else statement. Therefore, it is assumed that the content can also be registered as a user. Then describe the expected test results in the form expect (X) .to Y. This area is explained in a very easy-to-understand manner in the following article.
Reference => Introduction to RSpec that can be used, Part 1 "Understanding the basic syntax and useful functions of RSpec" This time, I expect the result that the user information contained in the instance variable can be newly registered without going through validation. Write the details of what you want to test between it ~ do. It is desirable for others to see what the test item is about.
Let's check if the test result is correct. Enter the following command in the terminal.
% bundle exec rspec spec/models/user_spec.rb
If the following is output to the terminal, it is registered correctly.
1 examples, 0 failure
Next, we will test the case where registration is not possible. By the way, there are normal system and abnormal system in the test, and the original purpose that confirms the function of the original purpose such as registering a new user who tested first is the normal system, and conversely it cannot be registered due to validation. It is called an abnormal system to make sure that it cannot fulfill. After this, we will test the abnormal system.
Let's write as follows.
spec/models/user_spec.rb
(Abbreviation)
context 'If you can register as a user' do
#Describe the item to be tested in it
it 'name, email, password, password_If confirmation is entered correctly, you can register a new one.' do
#If the user information created by FactoryBot is correct, it will not be validated and be_pass the test with valid
expect(@user).to be_valid
end
end
context 'If you cannot register as a user' do
#Tested that registration cannot be done due to validation when trying to register without entering the user name
it 'Cannot register if name is empty' do
#Update user's name to empty
@user.name = nil
# valid?Judge whether it passes validation, and if it does not pass, generate an error message
@user.valid?
# @user.errors.full_Display error message with messages
#Describe the error message displayed after include
#Name can if devise is not translated into Japanese'I think you get the error message t be blank
expect(@user.errors.full_messages).to include("Please enter your name")
end
end
To briefly explain, set the name to nil and use the valid? Method to generate an error message by validation. If Gem's'pry-rails' is installed, write binding.pry after user.valid ?, execute the test code, and when the process stops, enter user.errors in the console and an error will occur. After that, you can also check the error message by typing user.erros.full_messages. By the way, if the character string in the include of the above code is different even by one character, the following output will be output and the test will not pass.
Failures:
1) User#create If user registration is not possible Registration is not possible if name is empty
Failure/Error: expect(@user.errors.full_messages).to include("Name can't be blank")
expected ["Please enter your name"] to include "Name can't be blank"
# ./spec/models/user_spec.rb:28:in `block (4 levels) in <top (required)>'
This means that I expected the error message to be "Name can`t be blank", but it's actually "Enter Name". (I forgot that devise was translated into Japanese and made a mistake ...) When checking the error statement, the actual error message is output after expected, so if you copy and paste this, the test will pass.
This is the final code.
spec/models/user_spec.rb
require 'rails_helper'
#Describe what the test is in describe
#This time it is a test of the User model, so it looks like this
RSpec.describe User, type: :model do
#Create a new user(Registration)Because it is a test to do#I am creating
describe '#create' do
# spec/factories/user.Make the contents of rb a calling variable
#Not a local variable because the scope is different@To make it an instance variable
before do
@user = FactoryBot.build(:user)
end
#Describe the condition / situation in context
context 'If you can register as a user' do
#Describe the item to be tested in it
it 'name, email, password, password_If confirmation is entered correctly, you can register a new one.' do
#If the user information created by FactoryBot is correct, it will not be validated and be_pass the test with valid
expect(@user).to be_valid
end
end
context 'If you cannot register as a user' do
#Tested that registration cannot be done due to validation when trying to register without entering the user name
it 'Cannot register if name is empty' do
#Update user's name to empty
@user.name = nil
# valid?Judge whether it passes validation, and if it does not pass, generate an error message
@user.valid?
# @user.errors.full_Display error message with messages
#Describe the error message displayed after include
#Name can if devise is not translated into Japanese'I think you get the error message t be blank
expect(@user.errors.full_messages).to include("Please enter your name")
end
it 'Cannot register if email is empty' do
@user.email = nil
@user.valid?
expect(@user.errors.full_messages).to include("Please enter your email")
end
#By default, devise has a validation that the same content of uniqueness cannot be registered.
it 'The same email cannot be registered if it is already in use' do
#Prepare two users to confirm uniqueness
# @Save user
@user.save
#Create a second user
another_user = FactoryBot.build(:user)
# another_user's email@Update to the same email as user
another_user.email = @user.email
another_user.valid?
expect(another_user.errors.full_messages).to include("Email already exists")
end
it 'Cannot register if password is empty' do
@user.password = nil
@user.valid?
expect(@user.errors.full_messages).to include("Please enter your password", "Password (for confirmation) and password input do not match")
end
#By default, devise has a validation that you can not register unless 6 characters or more appear in password
it 'Cannot register if password is 6 characters or less' do
#Set password to 5 characters
@user.password = "12345"
@user.password_confirmation = "12345"
@user.valid?
expect(@user.errors.full_messages).to include("Please enter the password with at least 6 characters")
end:tired_face:
it 'password is correct_Cannot register if confirmation does not match' do
# #If you set this to nil, it will not pass the test.""Must be
@user.password_confirmation = ""
@user.valid?
expect(@user.errors.full_messages).to include("Password (for confirmation) and password input do not match")
end
end
end
end
What I couldn't think of at first was that I couldn't register if my email address was the same as other users. The point was to first create two users and update the first email address to the same value as the second email address, intentionally the same.
Another point is the last part, "Even if the password is correct, it cannot be registered unless password_confirmation matches". If you set the password confirmation to nil, it will not work, so you need to explicitly indicate that it is empty with "". On the contrary, other names and emails can be "".
The above is the contents of the unit test code of the User model implemented this time. I tried to write it in as much detail as possible, but it has become quite long, but next time I would like to make it easier to understand. Next, I wish I could write about the integration test code. I would appreciate it if you could point out any mistakes or confusing parts.
Reference => FactoryBot Github Reference => RSpec Github Reference => Faker Github
Recommended Posts