[RSpec on Rails] How to write test code for beginners by beginners

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"

1. Why do you need test code

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.

2. What is Rspec?

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.

3. About the contents of the application to be tested this time

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.

4. Development environment

・ MacOS Catalina 10.15.6 ・ Ruby 2.6.5 ・ Rails 6.0.3 ・ Gem FactoryBot ・ Gem RSpec ・ Gem Faker

5. Procedure

5-0. Preparation

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
5-1. RSpec settings

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.

5-2. FactoryBot settings

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.)

5-3. Write test code

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

[RSpec on Rails] How to write test code for beginners by beginners
[RSpec] How to write test code
Rails on Tiles (how to write)
How to write an RSpec controller test
How to write Rails
How to write test code with Basic authentication
How to build a Ruby on Rails environment using Docker (for Docker beginners)
[For Rails beginners] Summary of how to use RSpec (get an overview)
How to implement login request processing (Rails / for beginners)
[Ruby on Rails] How to write enum in Japanese
[Rails] Test code using Rspec
How to write Rails validation
How to write Rails seed
How to write Rails routing
[RSpec] How to test error messages set by Shoulda-Matchers
Explanation of Ruby on rails for beginners ④ ~ Naming convention and how to use form_Tag ~
How to test a private method with RSpec for yourself
[Rails] How to display error messages for comment function (for beginners)
How to deploy jQuery on Rails
[Rails] How to write in Japanese
How to use Ruby on Rails
How to deploy Bootstrap on Rails
[Rails] How to write exception handling?
How to write easy-to-understand code [Summary 3]
Must-see for beginners! How to manage your Xcode project on Github
Understand code coverage with Rspec, the Ruby on Rails test framework
[Ruby on Rails] How to avoid creating unnecessary routes for devise
[Rails] Unit test code for User model
Introduce RSpec and write unit test code
[Ruby on Rails] View test with RSpec
[Ruby on Rails] How to use CarrierWave
[Ruby on Rails] Controller test with RSpec
[Ruby] How to use slice for beginners
[Ruby on Rails] How to use redirect_to
Write code that is difficult to test
[Ruby on Rails] How to use kaminari
[Ruby on Rails] Model test with RSpec
Explanation of Ruby on rails for beginners ①
[SpringBoot] How to write a controller test
[For beginners] How to debug in Eclipse
Must-see for beginners! Specific method to manage X code project on Github ②
How to set different source / target versions for production code and test code
(For beginners) [Rails] Time saving tech! How to install and use slim
[Rspec] Flow from introducing Rspec to writing unit test code for a model
[Rails] How to decide the destination by "rails routes"
Rails: How to write a rake task nicely
[Java] How to test for null with JUnit
[Rails] Add page nation to table [For beginners]
[Rails] How to write when making a subquery
[Ruby on Rails] How to display error messages
How to add / remove Ruby on Rails columns
How to use "sign_in" in integration test (RSpec)
How to output CSV created by Rails to S3
JUnit 5: How to write test cases in enum
[Rails] How to implement unit tests for models
[For beginners] How to implement the delete function
Introducing Rspec, a Ruby on Rails test framework
How to separate .scss by controller in Rails
How to write code that thinks object-oriented Ruby
[Rails MySQL] How to reset DB on heroku
[Rails] How to use Gem'rails-i18n' for Japanese support