[RUBY] I tried unit testing Rails app using RSpec and FactoryBot

Overview

Since we tested the personally developed app created as a portfolio I would like to look back on the test flow as a personal output. This time, it will be the basic content of the test related to model validation.

Premise

Testing in programming means "making sure that your program works as intended."

environment

Ruby 2.5.1 Rails 5.2.3

User model created using gem'devise'

Introduction

First, install the Gem used for testing.

Gem used this time

--rspec-rails ▶ ︎RSpec, a gem for Rails, a language specialized for testing --factory_bot ▶ ︎ A gem that allows you to easily create a dummy instance

Gemfile


group :development, :test do
abridgement

  gem 'factory_bot_rails'
  gem 'rspec-rails'
end

Terminal


$ bundle install

This completes the gem installation.

Set RSpec

First, you need to generate a file for RSpec, so execute the following command.

Terminal


$ rails g rspec:install

This will generate the following files.

Terminal


create .rspec
create spec
create spec/spec_helper.rb
create spec/rails_helper.rb

Add the following description to .rspec

.rspec


--format documentation

At this point, you can run the test with the following command.

Terminal


$ bundle exec rspec

Write test code for model class

I'm finally writing the test code Place the spec file where you write the test code in the directory named spec that was generated along the way. Also, since the model spec file is put together in the model directory, let's create the directory and file with a text editor.

  1. Create a directory called models under the spec directory.
  2. Create a model spec file in the spec / models directory.

** * The naming convention for spec files is that the spec file will be named the corresponding class name_spec.rb. ** **

This time, we will proceed on the assumption that we will perform a test related to validation of the User model, so create user_spec.rb.

First of all, we will test one by one to see if each validation at the time of new user registration is applied. This time, the validation of the User model is under the following conditions.

app/models/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

  validates :nickname, :email, :password, :password_confirmation, presence: true
end

Test the 4 columns to which the validates presence: true is applied.


First, test the validation of the nickname with the basic description.

spec/models/user_spec.rb


require 'rails_helper'
describe User do
  describe '#create' do
    it "Cannot register without nickname" do
     user = User.new(nickname: "", email: "[email protected]", password: "1234567", password_confirmation: "1234567")
     user.valid?
     expect(user.errors[:nickname]).to include("can't be blank")
    end
  end
end

The first line describes the file to be read for testing. The second line describes the model class. The third line describes the action name to be tested.

The grouping of it to end on the 4th to 8th lines is the test code for each one. The 4th line describes the content to be tested. The 5th line describes the creation of the instance, but it is described with a realistic assumption except that the nickname is empty. In the 6th line, if you describe the valid? Method in the created instance, you can check "whether it can not be saved by validation" when saving the instance of the class that inherits ActiveRecord :: Base. .. The 7th line describes the expected test results for the contents of the 6th line. The return value of the valid? Method is true or false, but if you use the errors method for the instance that used the valid? Method, you can check why it cannot be saved if it cannot be saved due to validation. In this case, we use the include matcher below to to predict that "an error message" can't be blank "will appear".

Zackri in Japanese "Write an expectation that the user's nickname is empty and will return the error" Cannot be empty "." It's like

Let's run the test here

Terminal


$ bundle exec rspec

1 example, 0 failures

If you see the message "0 failures for one test" as shown above, it's OK.

I will also test other validations, By using factory_bot which was introduced first, the test code of the user instance creation part can be shared, which is quite convenient.

use factory_bot

First, let's create a directory called factories under the spec directory. Create a file called users.rb in it. ** * File naming is unified with model name plural **

Then, in an empty file, describe the common parts for creating a model instance.

spec/factories/users.rb


FactoryBot.define do

  factory :user do
    nickname              {"Taro Tanaka"}
    email                 {"[email protected]"}
    password              {"1234567"}
    password_confirmation {"1234567"}
  end

end

I don't have to explain anything in particular When you create an instance of the user model with the test code, it will be executed every time with the contents described.

#This is
user = User.new(nickname: "Taro Tanaka", email: "[email protected]", password: "1234567", password_confirmation: "1234567")
#This can be achieved
user = FactoryBot.build(:user)

What a further simplification of this code. There is a description to read'rails_helper'in user_spec.rb, We'll make a few tweaks to this rails_helper.rb file.

spec/rails_helper.rb


#abridgement
RSpec.configure do |config|
  #Added the following description
  config.include FactoryBot::Syntax::Methods

  #abridgement

end

Now you are ready to go. Then ...

#This is
user = User.new(nickname: "Taro Tanaka", email: "[email protected]", password: "1234567", password_confirmation: "1234567")
#This can be achieved
user = FactoryBot.build(:user)
#Can be omitted further
user = build(:user)

The amount of code is considerably reduced ...

When I first learned this, I put values in all the columns, but can I reproduce empty columns when testing validation? There was a question ➡︎ You can do it without problems

Let's look at the rest of the test code.

spec/models/user_spec.rb


require 'rails_helper'

describe User do
  describe '#create' do
    it "Cannot register without nickname" do
      user = build(:user, nickname: "")
      user.valid?
      expect(user.errors[:nickname]).to include("can't be blank")
    end

    it "Cannot register without email" do
      user = build(:user, email: "")
      user.valid?
      expect(user.errors[:email]).to include("can't be blank")
    end
    
    it "Cannot register without password" do
      user = build(:user, password: "")
      user.valid?
      expect(user.errors[:password]).to include("can't be blank")
    end

    it "password even if password exists_Cannot register without confirmation" do
      user = build(:user, password_confirmation: "")
      user.valid?
      expect(user.errors[:password_confirmation]).to include("doesn't match Password")
    end
    
    it "Cannot register if password is 5 characters or less" do
      user = build(:user, password: "00000", password_confirmation: "00000")
      user.valid?
      expect(user.errors[:password]).to include("is too short (minimum is 6 characters)")
    end

    #We also carry out tests when registration is possible
    it "nickname and email, password and password_You can register if confirmation exists" do
      user = build(:user)
      expect(user).to be_valid
    end

    it "You can register if the password is 6 characters or more" do
      user = build(:user, password: "000000", password_confirmation: "000000")
      user.valid?
      expect(user).to be_valid
    end
  end
end

The above explanation

user = build(:user)
user = build(:user, nickname: "")

By specifying the column name and value again as in the second line You can overwrite the preset value. In the case of this example, nickname: "Ichiro Tanaka" is overwritten with nickname: "". By doing this, you can flexibly change the preset value (nil is also acceptable).

it "password even if password exists_Cannot register without confirmation" do
      user = build(:user, password_confirmation: "")
      user.valid?
      expect(user.errors[:password_confirmation]).to include("doesn't match Password")
    end

Also, the above include matcher part Like "can't be blank" The error message "doesn't match Password" Where are you from? I'm thinking of writing something like that If you don't know from the beginning, you can't predict the error message ...?

There was a time when I thought so ** (Note: I'm still a beginner) **


Although these error messages are originally provided by Rails Gem, Ultimately, you can still run the test

.to include("")

Then, the test execution result will be displayed in the terminal. You expected to get the error message "", but the actual error message was "doesn't match Password"! Will be returned.

Then Ah! Yes Yes! That's what Wai wanted to write in the test! You understand

You can also open the console to verify and confirm the flow, If you are a beginner and have a small amount of testing for personally developed apps, this method seems to be sufficient. (Looks angry)

Also, the last two tests are tests when user registration is possible. There is a be_valid matcher. This is used when you say "it will clear all validations".

Finally try running the test

Terminal


$ bundle exec rspec

7 example, 0 failures

If there are no failures for the number of tests, it's OK.

Summary

Using RSpec and FactoryBot, I looked back on a simple Rails model test. The test I conducted this time is only a part, but I wrote the basic flow and small stories, so I hope it will help those who are new to learning.
Also, if you find any inadequacies in the contents, please let us know. Finally, I would like to mention the Qiita article that I referred to during my study. Thank you very much.

Reference article

-[Introduction to RSpec that can be used, Part 1 "Understanding the basic syntax and useful functions of RSpec"](https://qiita.com/jnchito/items/42193d066bd61c740612#describe--it--expect-%E3% 81% AE% E5% BD% B9% E5% 89% B2% E3% 82% 92% E7% 90% 86% E8% A7% A3% E3% 81% 99% E3% 82% 8B) -[Introduction to RSpec that can be used, part 2 "Mastering frequently used matchers"](https://qiita.com/jnchito/items/2e79a1abe7cd8214caa5#%E4%BB%8A%E5%9B%9E%E8%AA% AC% E6% 98% 8E-% E3% 81% 97% E3% 81% AA% E3% 81% 84-% E5% 86% 85% E5% AE% B9)

Recommended Posts

I tried unit testing Rails app using RSpec and FactoryBot
About app testing RSpec (unit test)
[Rails] I tried to implement "Like function" using rails and js
[RSpec] Unit test (using gem: factory_bot)
I tried to introduce CircleCI 2.0 to Rails app
I tried barcode scanning using Rails + React + QuaggaJS
[Android] I quit SQLite and tried using Realm
I tried using Gson
I tried using TestNG
I tried using Galasa
[Rails] From test preparation to model unit testing [RSpec]
[Rails / RSpec] Write Model test (using Shoulda Matchers / FactoryBot)
I tried using YOLO v4 on Ubuntu and ROS
I tried to introduce Bootstrap 4 to the Rails 6 app [for beginners]
[Rails] I tried using the button_to method for the first time
[Rails] I tried to create a mini app with FullCalendar
What I was addicted to while using rspec on rails
[RSpec] Let's use FactoryBot [Rails]
I tried using azure cloud-init
I tried using Hotwire to make Rails 6.1 scaffold a SPA
I tried Rails beginner [Chapter 1]
[Rails] Test code using Rspec
I tried using Apache Wicket
I tried using Java REPL
I tried Rails beginner [Chapter 2]
I tried to integrate Docker and Maven / Netbean nicely using Jib
I tried to build a simple application using Dockder + Rails Scaffold
I tried using anakia + Jing now
I tried using Spring + Mybatis + DbUnit
Rails book review app RSpec introduction
I tried using JOOQ with Gradle
[Rails] I tried deleting the application
[Rails5] Rspec -Unit test when nesting-
I tried using Java8 Stream API
I tried using JWT in Java
[Android] I tried using Coordinator Layout.
I tried using Pari gp container
I tried using WebAssembly Stadio (2018/4/17 version)
I tried using Java memo LocalDate
Let's unit test with [rails] Rspec!
I tried using GoogleHttpClient of Java
Rails API mode I tried to implement the keyword multiple search function using arrays and iterative processing.
I introduced WSL2 + Ubuntu to Window10 and tried using GDC, DMD, LDC
I created and set my own Dialect with Thymeleaf and tried using it
I tried to make my own transfer guide using OpenTripPlanner and GTFS
Spring Flash Scope Redirection and Unit Testing
I tried using Elasticsearch API in Java
I tried using Realm with Swift UI
Introduce RSpec and write unit test code
I tried using Java's diagnostic tool Arthas
I tried using UICollectionViewListCell added from Xcode12.
I tried using Scalar DL with Docker
I tried using OnlineConverter with SpringBoot + JODConverter
It's new, but I tried using Groonga
I tried using OpenCV with Java + Tomcat
Notes on creating a many-to-many Factory with Rspec and testing it with SystemSpec [RSpec, FactoryBot]
I tried using "nifty cloud mobile backend" and "Firebase" Authentication on Kotlin + Android
[JDBC ③] I tried to input from the main method using placeholders and arguments.