[RUBY] [Rails] How to implement unit tests for models

Prerequisites

This time, we will output the method while implementing the unit test of the model for new user registration. The specifications for user registration are as follows.

Nickname required
Email address is unique
mail address is@And domain must be included
Password required
Password must be at least 7 characters
Enter the password twice, including for confirmation
User's real name is required for surname and first name
Enter the user's real name in full-width
User's real name Frigana is required for surname and first name respectively
Let the user's real name Frigana be entered in full-width
Date of birth is required

The migration file is as follows.

20200823050614_devise_create_users.rb


class DeviseCreateUsers < ActiveRecord::Migration[5.2]
  def change
    create_table :users do |t|
      ## Database authenticatable
      t.string  :nickname,              null: false
      t.string  :email,                 null: false, default: ""
      t.string  :encrypted_password,    null: false, default: ""
      t.string  :family_name,           null: false
      t.string  :family_name_kana,      null: false
      t.string  :first_name,            null: false
      t.string  :first_name_kana,       null: false
      t.date    :birth_day,             null: false

      ## Recoverable
      t.string   :reset_password_token
      t.datetime :reset_password_sent_at

      ## Rememberable
      t.datetime :remember_created_at

      ## Trackable
      # t.integer  :sign_in_count, default: 0, null: false
      # t.datetime :current_sign_in_at
      # t.datetime :last_sign_in_at
      # t.string   :current_sign_in_ip
      # t.string   :last_sign_in_ip

      ## Confirmable
      # t.string   :confirmation_token
      # t.datetime :confirmed_at
      # t.datetime :confirmation_sent_at
      # t.string   :unconfirmed_email # Only if using reconfirmable

      ## Lockable
      # t.integer  :failed_attempts, default: 0, null: false # Only if lock strategy is :failed_attempts
      # t.string   :unlock_token # Only if unlock strategy is :email or :both
      # t.datetime :locked_at
      t.timestamps null: false
    end

    add_index :users, :email,                 unique: true
    add_index :users, :reset_password_token,  unique: true
    # add_index :users, :confirmation_token,   unique: true
    # add_index :users, :unlock_token,         unique: true
  end
end

The model is as follows.

user.rb


class User < ApplicationRecord
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable

    #Validation settings(Uniqueness constraint to prevent empty string from being saved)
    validates :nickname,               presence: true
    validates :encrypted_password,     presence: true
    validates :family_name,            presence: true
    validates :family_name_kana,       presence: true
    validates :first_name,             presence: true
    validates :first_name_kana,        presence: true
    validates :birth_day,              presence: true
    
    #association
    has_many :cards,          dependent: :destroy
    has_many :shipping_infos, dependent: :destroy
    has_many :comments,       dependent: :destroy
    has_many :items
end

Implementation procedure

** ① Preparation for RSpec ** ** ② RSpec settings ** ** ③ Introduce factory_bot ** ** ④ Implementation of factory_bot ** ** ⑤ Regular expression description ** ** ⑥ Implementation of test code ** ** ⑦ Execution of test code **

RSpec preparation

First install Gem.

Gemfile.rb


group :development, :test do
  
  #abridgement

  gem 'rspec-rails'
end

group :development do
  gem 'web-console'
end

# web_If console already has a description, just move the description location. Please note that if you add it, it may be duplicated.

After editing the Gemfile, don't forget to run bundle install.

RSpec settings

Next, make basic settings for RSpec. First, create a configuration file for RSpec.

Terminal.


$ rails g rspec:install

#Success if the file is created as follows ▼
 create  .rspec
 create  spec
 create  spec/spec_helper.rb
 create  spec/rails_helper.rb

Next, add the following to .rspec.

.rspec.


--format documentation

Next, set the directory for RSpec.

The file in which the test code by RSpec is written is called a spec file, and all spec files are stored in the spec directory generated by the rails g rspec: install command, so create it here. Let's keep it.

Test files for models are stored under spec / models /, and test files for controllers are stored under spec / controllers /. This directory is where all the code under the app directory to be tested is located.

The spec file will be named _spec.rb with the corresponding class. This time, we will first create a spec file for "user.rb", so the name in that case will be " user_spec.rb ".

After creating the directory, check once at this point whether ** RSpec ** can be used normally.

Terminal.


$ bundle exec rspec

#If the result is as follows, it is OK because it is operating normally.
No examples found.

Finished in 0.00031 seconds (files took 0.19956 seconds to load)
0 examples, 0 failures

Introduced factory_bot

Then install ** factory_bot **. Install Gem.

Gemfile.rb


group :development, :test do
  
  #abridgement

  gem 'rspec-rails'
  gem 'factory_bot_rails'
end

Then run bundle install.

Implementation of factory_bot

Next, create a directory called factories directly under the spec directory, and create a Ruby file in it with the plural file name of the created instance. In this case, it will be ʻusers.rb`.

Edit ʻusers.rb` as follows.

users.rb


FactoryBot.define do

  factory :user do
    nickname              {"tarou"}
    email                 {"[email protected]"}
    password              {"1234567"}
    encrypted_password    {"1234567"}
    family_name           {"Yamada"}
    family_name_kana      {"Yamada"}
    first_name            {"Taro"}
    first_name_kana       {"Taro"}
    birth_day             {"2000-01-01"}
  end
end

This description creates the contents according to the specifications as dummy data, so that you can easily create an instance by a specific method (build or create) in the spec file or save it in the DB. It will be like.

Regular expression description

Next, set the regular expression in the validation according to the specifications. The required regular expressions are:

Password must be at least 7 characters
Enter the user's real name in full-width
Let the user's real name Frigana be entered in full-width

Based on these, the description in the model is as follows.

user.rb


class User < ApplicationRecord
    #abridgement

    validates :nickname,               presence: true
    validates :encrypted_password,     presence: true, length: { minimum: 7 } #Here is the regular expression for the number of characters
    validates :family_name,            presence: true, format: {with: /\A[Ah-Hmm-One-龥]/ } #This is the user's real name full-width regular expression
    validates :family_name_kana,       presence: true, format: {with: /\A[A-Car-]+\z/ } #Here is the Frigana full-width regular expression
    validates :first_name,             presence: true, format: {with: /\A[Ah-Hmm-One-龥]/ } #This is the user's real name full-width regular expression
    validates :first_name_kana,        presence: true, format: {with: /\A[A-Car-]+\z/ } #Here is the Frigana full-width regular expression

    #abridgement
end

Please refer to the following site for Ruby regular expressions. https://gist.github.com/nashirox/38323d5b51063ede1d41

Test code implementation

Now it's time to implement the test code! Here are some things to keep in mind when writing test code:

(1) One value is expected in each example
② Describe the expected result clearly and clearly
③ Test both what you want to happen and what you don't want to happen
④ Make it DRY moderately while considering readability

Write the following code in user_spec.rb.

user_spec.rb


require 'rails_helper'

describe User do
  describe '#create' do

  end
end

Since this is a unit test of the User model, it will be describe User do, and since a new user will be created, it will be describe'# create' do, and the test code will be written in the describ on the second line.

First, test that ** can be registered if all items are entered **. The code and its explanation are as follows.

user_spec.rb


require 'rails_helper'

describe User do
  describe '#create' do
    #Test when entered ▼

    it "Registration is possible if all items are entered" do #What you want to test
      user = build(:user)  #Using the build method on the variable user, factory_Substitute bot dummy data
      expect(user).to be_valid #Execute a test to see if registration is done with the information of the variable user
    end
  end
end

The next test is as follows.

Cannot register without nickname
Cannot register without email
Cannot register without password
encrypted_Cannot register without password
family_Cannot register without name
family_name_Cannot register without kana
first_Cannot register without name
first_name_Cannot register without kana
birth_Cannot register if there is no day

These conditions are for testing the required items (null: false, presence: true) in the specification. Let's describe it. The explanation will be given in the test part of ** Cannot be registered without nickname **.

user.spec.rb


require 'rails_helper'

describe User do
  describe '#create' do

      #Test when entered ▼

      it "Registration is possible if all items are entered" do
        user = build(:user)
        expect(user).to be_valid
      end

      # nul:false, presence:True test ▼

      it "Cannot register without nickname" do #What you want to test
        user = build(:user, nickname: nil) #Using the build method on the variable user, factory_Substitute bot dummy data(In this case, intentionally set the value of nickname to)
        user.valid? #Test "Is it in a state where it cannot be saved by validation" using the validation method?
        expect(user.errors[:nickname]).to include("Please enter") #Use the errors method to check "Why can't I save if validation doesn't work?".to include("Please enter")Write the error statement in(Since Rails is translated into Japanese, the error text is also in Japanese.)
      end

      it "Cannot register without email" do
        user = build(:user, email: nil)
        user.valid?
        expect(user.errors[:email]).to include("Please enter")
      end

      it "Cannot register without password" do
        user = build(:user, password: nil)
        user.valid?
        expect(user.errors[:password]).to include("Please enter")
      end

      it "encrypted_Cannot register without password" do
        user = build(:user, encrypted_password: nil)
        user.valid?
        expect(user.errors[:encrypted_password]).to include("Please enter")
      end

      it "family_Cannot register without name" do
        user = build(:user, family_name: nil)
        user.valid?
        expect(user.errors[:family_name]).to include("Please enter")
      end

      it "family_name_Cannot register without kana" do
        user = build(:user, family_name_kana: nil)
        user.valid?
        expect(user.errors[:family_name_kana]).to include("Please enter")
      end

      it "first_Cannot register without name" do
        user = build(:user, first_name: nil)
        user.valid?
        expect(user.errors[:first_name]).to include("Please enter")
      end

      it "first_name_Cannot register without kana" do
        user = build(:user, first_name_kana: nil)
        user.valid?
        expect(user.errors[:first_name_kana]).to include("Please enter")
      end

      it "birth_Cannot register if there is no day" do
        user = build(:user, birth_day: nil)
        user.valid?
        expect(user.errors[:birth_day]).to include("Please enter")
      end
  end
end

Next, we will test the uniqueness constraint of email.

user.spec.rb


require 'rails_helper'

describe User do
  describe '#create' do

      #abridgement

      #Email uniqueness constraint test ▼

      it "Cannot register if there are duplicate emails" do
        user = create(:user) #Factory to variable user and database using create method_Save bot dummy data
        another_user = build(:user, email: user.email) #Second another_Create user as a variable and use build method to intentionally duplicate email content
        another_user.valid? # another_Test user's "Cannot save due to validation"
        expect(another_user.errors[:email]).to include("Already exists") #Use the errors method to check the email "Why can't I save due to validation?" And write the error statement that caused it.
      end
  end
end

Next, run a test that cannot be registered without a confirmation password.

user.spec.rb


require 'rails_helper'

describe User do
  describe '#create' do

      #abridgement

      #Test that requires confirmation password ▼
      
      it "encrypted even if password exists_Cannot register without password" do
        user = build(:user, encrypted_password: "") #Intentionally empty the confirmation password
        user.valid?
        expect(user.errors[:encrypted_password]).to include("Please enter", "Please enter at least 7 characters")
      end
  end
end

At this point, the following tests have been completed.

#Completed test ▼

Nickname required#Done
Email address is unique#Done
mail address is@And domain must be included#Done
Password required#Done
Enter the password twice, including for confirmation#Done
User's real name is required for surname and first name#Done
User's real name Frigana is required for surname and first name respectively#Done
Date of birth is required#Done

#Test to be implemented ▼

Password must be at least 7 characters
Enter the user's real name in full-width
Let the user's real name Frigana be entered in full-width

Now, let's test the regular expression described earlier. First of all, the password is a test of 7 characters or more.

Test both ** what you want to happen and what you don't want to happen **.

user.spec.rb


require 'rails_helper'

describe User do
  describe '#create' do

      #abridgement

      #Password character count test ▼

      it "You can register if the password is 7 characters or more" do
        user = build(:user, password: "1234567", encrypted_password: "1234567") #Set a 7-character password using the build method
        expect(user).to be_valid
      end
      
      it "Cannot register if password is 7 characters or less" do
        user = build(:user, password: "123456", encrypted_password: "123456") #Intentionally set a 6-character password to test if an error occurs
        user.valid?
        expect(user.errors[:encrypted_password]).to include("Please enter at least 7 characters")
      end
  end
end

Finally, we will test full-width input and full-width kana input.

user.spec.rb


require 'rails_helper'

describe User do
  describe '#create' do

      #abridgement

      #Name full-width input test ▼

      it 'family_Registration is not possible unless name is entered in full-width' do
        user = build(:user, family_name: "Aio") #Intentionally enter half-width characters to generate an error
        user.valid?
        expect(user.errors[:family_name]).to include("Is an invalid value")
      end

      it 'first_Registration is not possible unless name is entered in full-width' do
        user = build(:user, first_name: "Aio") #Intentionally enter half-width characters to generate an error
        user.valid?
        expect(user.errors[:first_name]).to include("Is an invalid value")
      end

      #Katakana full-width input test ▼

      it 'family_name_Only full-width katakana can be registered for kana' do
        user = build(:user, family_name_kana: "AIUEO") #Intentionally input hiragana to generate an error
        user.valid?
        expect(user.errors[:family_name_kana]).to include("Is an invalid value")
      end
      
      it 'first_name_Only full-width katakana can be registered for kana' do
        user = build(:user, first_name_kana: "AIUEO") #Intentionally input hiragana to generate an error
        user.valid?
        expect(user.errors[:first_name_kana]).to include("Is an invalid value")
      end
  end
end

This completes the functional test according to the specifications. The final result is as follows.

user.spec.rb


require 'rails_helper'

describe User do
  describe '#create' do

      #Test when entered ▼

      it "Registration is possible if all items are entered" do
        user = build(:user)
        expect(user).to be_valid
      end

      # nul:false, presence:True test ▼

      it "Cannot register without nickname" do
        user = build(:user, nickname: nil)
        user.valid?
        expect(user.errors[:nickname]).to include("Please enter")
      end

      it "Cannot register without email" do
        user = build(:user, email: nil)
        user.valid?
        expect(user.errors[:email]).to include("Please enter")
      end

      it "Cannot register without password" do
        user = build(:user, password: nil)
        user.valid?
        expect(user.errors[:password]).to include("Please enter")
      end

      it "encrypted_Cannot register without password" do
        user = build(:user, encrypted_password: nil)
        user.valid?
        expect(user.errors[:encrypted_password]).to include("Please enter")
      end

      it "family_Cannot register without name" do
        user = build(:user, family_name: nil)
        user.valid?
        expect(user.errors[:family_name]).to include("Please enter")
      end

      it "family_name_Cannot register without kana" do
        user = build(:user, family_name_kana: nil)
        user.valid?
        expect(user.errors[:family_name_kana]).to include("Please enter")
      end

      it "first_Cannot register without name" do
        user = build(:user, first_name: nil)
        user.valid?
        expect(user.errors[:first_name]).to include("Please enter")
      end

      it "first_name_Cannot register without kana" do
        user = build(:user, first_name_kana: nil)
        user.valid?
        expect(user.errors[:first_name_kana]).to include("Please enter")
      end

      it "birth_Cannot register if there is no day" do
        user = build(:user, birth_day: nil)
        user.valid?
        expect(user.errors[:birth_day]).to include("Please enter")
      end
      
      #Password character count test ▼

      it "You can register if the password is 7 characters or more" do
        user = build(:user, password: "1234567", encrypted_password: "1234567")
        expect(user).to be_valid
      end
      
      it "Cannot register if password is 7 characters or less" do
        user = build(:user, password: "123456", encrypted_password: "123456")
        user.valid?
        expect(user.errors[:encrypted_password]).to include("Please enter at least 7 characters")
      end

      #Email uniqueness constraint test ▼

      it "Cannot register if there are duplicate emails" do
        user = create(:user)
        another_user = build(:user, email: user.email)
        another_user.valid?
        expect(another_user.errors[:email]).to include("Already exists")
      end

      #Test that requires confirmation password ▼
      
      it "encrypted even if password exists_Cannot register without password" do
        user = build(:user, encrypted_password: "")
        user.valid?
        expect(user.errors[:encrypted_password]).to include("Please enter", "Please enter at least 7 characters")
      end

      #Identity verification name full-width input test ▼

      it 'family_Registration is not possible unless name is entered in full-width' do
        user = build(:user, family_name: "Aio")
        user.valid?
        expect(user.errors[:family_name]).to include("Is an invalid value")
      end

      it 'first_Registration is not possible unless name is entered in full-width' do
        user = build(:user, first_name: "Aio")
        user.valid?
        expect(user.errors[:first_name]).to include("Is an invalid value")
      end

      #Identity verification Katakana full-width input test ▼

      it 'family_name_Only full-width katakana can be registered for kana' do
        user = build(:user, family_name_kana: "AIUEO")
        user.valid?
        expect(user.errors[:family_name_kana]).to include("Is an invalid value")
      end
      
      it 'first_name_Only full-width katakana can be registered for kana' do
        user = build(:user, first_name_kana: "AIUEO")
        user.valid?
        expect(user.errors[:first_name_kana]).to include("Is an invalid value")
      end
  end
end

Execution of test code

Execute the following command in the terminal of the directory of the application that describes the test.

Terminal.


$ bundle exec rspec         

Then, if the processing is performed as shown in the image below, the test is successful. image.png

Finally

If you can understand the basic grammar, the process that causes an error and the process that does not generate an error, and understand each method, you can write code even if a new function is added.

If you have any concerns, please let us know in the comments! Thank you for watching until the end!

Recommended Posts

[Rails] How to implement unit tests for models
[Rails] How to implement scraping
How to implement login request processing (Rails / for beginners)
[Rails] How to implement star rating
How to implement search functionality in Rails
How to implement ranking functionality in Rails
How to implement image posting using rails
How to write Rails
[For beginners] How to implement the delete function
How to uninstall Rails
How to implement a like feature in Rails
[Rails] How to easily implement numbers with pull-down
[Rails] How to use Gem'rails-i18n' for Japanese support
How to implement Pagination in GraphQL (for ruby)
[Rails] How to create a signed URL for CloudFront
How to implement a like feature in Ajax in Rails
How to write a unit test for Spring Boot 2
[Rails, JS] How to implement asynchronous display of comments
How to make Java unit tests (JUnit & Mockito & PowerMock)
Rails learning How to implement search function using ActiveModel
(Ruby on Rails6) How to create models and tables
[rails] How to post images
[Rails] How to use enum
[Rails] How to install devise
[Rails] How to use enum
How to read rails routes
How to use rails join
How to terminate rails server
How to write Rails validation
How to write Rails seed
[Rails] How to use validation
[Rails] How to disable turbolinks
[Rails] How to use authenticate_user!
[Rails] How to use "kaminari"
[Rails] How to make seed
How to write Rails routing
[Rails] How to install simple_calendar
[Java] How to implement multithreading
[Rails] How to install reCAPTCHA
[Rails] How to use Scope
[Ruby on Rails] How to implement tagging / incremental search function for posts (without gem)
[Rails] How to display error messages for comment function (for beginners)
[Rails] How to use gem "devise"
How to deploy jQuery on Rails
[Rails] How to install Font Awesome
How to unit test Spring AOP
[Rails] How to use devise (Note)
[Rails] How to use flash messages
[rails] How to display db information
How to specify validation for time_field
How to install JMeter for Mac
[Rails] How to write in Japanese
[Rails] How to prevent screen transition
How to use Ruby on Rails
How to deploy Bootstrap on Rails
[Rails] How to speed up docker-compose
Read H2 database for unit tests
[Rails] How to add new pages
Rails on Tiles (how to write)
[Rails] How to write exception handling?
[Rails] How to install ImageMagick (RMajick)