[RUBY] [Rails] Test with RSpec

Introduction

Tests that I have avoided because I thought it would be difficult. I'm about to start the service in earnest, so I'll write about what I learned as the first step to a solid understanding of testing. It's a so-called memorandum. Since I am a beginner myself, I will write from a beginner's point of view. I would be grateful if you could comment if there are more writing styles and methods like this!

Introducing RSpec

We will introduce the rules. Well, without thinking about anything here

Gemfile


group :development, :test do
  ...abridgement...
  gem 'rspec-rails'
end

group :development do
  gem 'web-console' #It may have been originally listed.
end


After editing the Gemfile, do the usual bundle install.

RSpec basic settings

First, you need to create a configuration file for RSpec. At the terminal

$ rails g rspec:install

Then the following files will be generated.

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

Add the following to the ***. Rspec *** file of the generated files.

.rspec


--format documentation

If you add the above, the test result will be output in an easy-to-read manner. You can think of it as writing the context and it that will come out later in stages. Well, I don't think it's a problem without it. By the way, if you add options other than the above, you can also output text and display in parallel, so if you want to have it, please check it out.

For the time being, the installation is complete. It is the work to write the test code from here.

Try running the test

RSpec can be executed by typing a command in the terminal. I haven't written any test code yet, but type the following code into the terminal to see if it actually works.

Terminal


$ bundle exec rspec

The above is the command to run the test. As a result, it is okay if the display below is displayed.

Terminal


No examples found.


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

From here, create test files such as models, controllers, roots, and views, and write test code.

Unit test (model)

The goal is to write and execute test code for model validation, which is a unit test.

What you need to know in advance

The test code creates a spec file and writes it. Instead of creating and writing one file, place the spec file under spec / models / for the model test file and under spec / controllers / for the controller test file for easy understanding. I will.

*** Spec file naming convention *** The spec file will be named the corresponding class name_spec.rb. This time we will be testing the validation of the contact form (contact model / contact.rb), so the name will be "contact_spec.rb".

Test code basics

Now that you know about the file where you write the test code, write the super super super basic test code before actually writing it. *** Simple test code "Make sure 1 + 1 becomes 2" ***

sample_spec.rb


describe "hogehoge" do
  it "1 +1 becomes 2" do
    expect(1 + 1).to eq 2
  end
end

Ignore vocabulary and give your own commentary.

sample_spec.rb



describe "hogehoge" do        #describe(Meaning to explain)Create a group for this test.
  it "1 +1 becomes 2" do     #it "~~~"do~~~Write what test this code is doing in the part
    expect(1 + 1).to eq 2     #This part is the formula that checks whether the test actually succeeds.
  end
end

The formula of the above expect (1 + 1) .to eq 2 part is called expectation. ● expect(X).to eq Y  If the value of the expression in the x part is equal to the value in the Y part like this, the test will succeed. The eq part is called the matcher.

A matcher is a condition in which a test is successful in an speculation. For example, as mentioned above, eq means "if equal". There are multiple other matchers such as include (if included) and valid (if validated). There are many, so please use it according to the test you want to write.

Actually test the validation of the model.

The contact model (inquiry function) used this time is as follows.

contact.rb



class Contact < ApplicationRecord

#Sender's name
  validates :name, presence: true 

#E-mail address to reply to inquiries
  validates :email, presence: true, length: {maximum:255},
                                    format: {with: /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i}

#Inquiry subject
  validates :title, presence: true

#Content of inquiry
  validates :message, presence: true
end

We will write test code for the above validation.

Create a spec / models / contact_spec.rb file.

Edit the created contact_spec.rb as follows.

contact_spec.rb


require 'rails_helper'
  RSpec.describe Contact, type: :model do
    it "Cannot register without name" do
  end
end

The first line require'rails_helper' enables common settings by reading the description in rails_helper.rb. This first line description is written to every spec file every time.

Once I wrote it, execute the command bundle exec rspec in the terminal. I haven't written an expression that evaluates as a test yet, so the test passes unconditionally. It's okay if it looks like the following.

Terminal

Terminal


Contact
Cannot register without name

Finished in 0.23784 seconds (files took 4.71 seconds to load)
1 example, 0 failures

Write test code to make sure you can't register if name is empty

Add the following to the test code you wrote earlier.

contact_spec.rb



RSpec.describe Contact, type: :model do
  it "Cannot register without name" do
    contact = Contact.new(name: "", email: "[email protected]", title: "About returns", message: "I would like to return the item with order number 000000000.")
    contact.valid?
    expect(contact.errors[:name]).to include("Please enter")
  end
end

*** [3rd line] Create a new instance of contact class with the property you want to test in ***. I want to create a test code to confirm that "cannot be registered if name is empty", so I am creating an instance of contact class with the value of name empty and other values set appropriately.

*** [6th line] Check if the instance created in *** cannot be saved by validation.

As a result of checking with *** [7th line] ***, check whether the error statement of the instance is what you expected. If you specify the column name in the hash value retrieval method for contact.errors, you can retrieve the array containing the error statement caused by that column. For this, we are making an exemption using a matcher called include. This time, "Please enter" is output in the validation error statement, so the content is described.

The above flow has the same meaning as the verification on the console below.

Terminal



#Launch the console
$ rails c
#Create an instance of the contact class with an empty name value
>contact = Contact.new(name: "", email: "[email protected]", title: "About returns", message: "I would like to return the item with order number 000000000.")
#valid?Use the method
>contact.valid?
#Use the errors method
>contact.errors
=> #<ActiveModel::Errors:0x007ffa6ce07ef0
 @base=
  #<Contact:0x007ffa6d3430b8
#Omission
 @messages={:name=>["Please enter"]}>

Now enter the RSpec command to run the test.

Terminal


$ bundle exec rspec

If you get the following result, you are successful.

Terminal


Contact
Cannot register without name

Finished in 0.07346 seconds (files took 2.31 seconds to load)
1 example, 0 failures

If an error is displayed, an error message will be displayed in the terminal and can often be resolved by reading it carefully. Also, as mentioned above, if you check it once as a series of flows on the console, I feel that your understanding will deepen a little.

[To be efficient] Introducing factory_bot

I wrote one test code earlier, but when writing a test for mail other than name, it will be as follows.

contact_spec.rb



RSpec.describe Contact, type: :model do
  it "Cannot register without name" do
    contact = Contact.new(name: "", email: "[email protected]", title: "About returns", message: "I would like to return the item with order number 000000000.")
    contact.valid?
    expect(contact.errors[:name]).to include("Please enter")
  end

  it "Cannot register without email" do
    contact = Contact.new(name: "takashi", email: "", title: "About returns", message: "I would like to return the item with order number 000000000.")     
    contact.valid?
    expect(contact.errors[:email]).to include("Please enter")
  end
end

I'm simply emptying the email part, but creating an instance each time increases the amount of code. *** factory_bot *** improves this.

What is factory_bot

A Gem that allows you to easily create a dummy instance. Set the properties defined for each class instance in advance in another file, and use the method from the spec file to create the exact instance.

If you use factory_bot, the description will be as follows.

contact_spec.rb



RSpec.describe Contact, type: :model do
  it "Cannot register without name" do
    contact = build(:contact, name: "")
    contact.valid?
    expect(contact.errors[:name]).to include("Please enter")
  end

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

I created an instance in a separate file in advance and called it.

Installation procedure

*** Added gem'factory_bot_rails' *** to the same environment as rspec-rails

Gemfile


group :development, :test do
  #abridgement
  gem 'rspec-rails'
  gem 'factory_bot_rails'
end

Then bundle install and Create a directory called "factories" directly under the spec directory. In it, create a Ruby file with the plural filename of the created instance. In this case, it's contacts.rb.

Edit the created contacts.rb file as follows.

contacts.rb


FactoryBot.define do
  factory :contact do
    name {"takashi"}
    email {"[email protected]"}
    title {"About returns"}
    message {"I would like to return the item with order number 000000000."}
  end
end

Then, when creating an instance, you can create an instance with the contents of the set template by writing the following.

#factory_When not using a bot
contact = Contact.new(name: "", email: "[email protected]", title: "About returns", message: "I would like to return the item with order number 000000000.")
#factory_When using a bot
contact = FactoryBot.build(:contact)

It was pretty refreshing. Also, by editing spec / rails_helper.rb as follows, you can omit the description of FactoryBot of the class that is the receiver.

rails_helper.rb


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

  #abridgement
end

Based on the above, I will write additional test code.

contact_spec.rb



require 'rails_helper'

RSpec.describe Contact, type: :model do
  it "Cannot register without name" do
    contact = build(:contact, name: "")
    contact.valid?
    expect(contact.errors[:name]).to include("Please enter")
  end

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

  it "@Domain after the mark" do
    contact = build(:contact, email: "example@eee")
    contact.valid?
    expect(contact.errors[:email]).to include("Is an invalid value")
  end

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

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


This is the completed form for the time being.

At the end

This time, we ran a test on the validation of the contact model, but there are various ways to write it depending on the specifications of the WEB application. I hope it helps you to understand the world of testing as much as possible. It seems that the method of testing the controller will be different, so I hope to improve it soon.

As I understood the test a little, I came to think that when developing the next application, I would like to write the test code and then implement it. We would appreciate it if you could comment on any corrections or corrections to the content of the article.

Thank you for reading for a long time!

Recommended Posts

[Rails] Test with RSpec
Let's unit test with [rails] Rspec!
Test Nokogiri with Rspec.
[Ruby on Rails] Controller test with RSpec
[Ruby on Rails] Model test with RSpec
I rewrote the Rails tutorial test with RSpec
Test Active Strage with RSpec
[Rails] Test code using Rspec
Test GraphQL resolver with rspec
[Rails] About Rspec response test
[Rails5] Rspec -Unit test when nesting-
Copy and paste test with RSpec
Rails tutorial test
Rspec Basics [Rails]
[Rails5] Rspec -validation-
About RSpec (Rails)
rails test fails with database reference error
Understand code coverage with Rspec, the Ruby on Rails test framework
I want to test Action Cable with RSpec test
RSpec test code execution
Integration Test with Gradle
[For beginners] Test devise user registration with RSpec
Introduction to RSpec 1. Test, RSpec
Test run on rails
[Rails 6] RuntimeError with $ rails s
Testing model with RSpec
Handle devise with Rails
[Rails] Learning with Rails tutorial
Introducing Rspec, a Ruby on Rails test framework
Use webmock with Rspec
[Rails] Development with MySQL
Automatically test with Gauge
Load test with JMeter
Rails beginners tried to get started with RSpec
Rails, RSpec installation procedure
Unit test with Junit.
Supports multilingualization with Rails!
Double polymorphic with Rails
[Rails] I want to test with RSpec. We support your step [Introduction procedure]
[Rails] From test preparation to model unit testing [RSpec]
[Rails] Test of star evaluation function using Raty [Rspec]
Run Ruby on Rails RSpec tests with GitHub Actions
[Rails / RSpec] Write Model test (using Shoulda Matchers / FactoryBot)
Rails5 Rspec test error ArgumentError: Factory not registered: user
Run RSpec, Brakeman, Rubocop, Rails Best Practices with overcommit
Introduction to RSpec 4. Create test data with Factory Bot
rails test db only drop
Check CSV value with RSpec
Introduced graph function with rails
[Rails] Express polymorphic with graphql-ruby
[Rails] Upload videos with Rails (ActiveStorage)
[Vue Rails] "Hello Vue!" Displayed with Vue + Rails
[RSpec] Let's use FactoryBot [Rails]
Preparation for developing with Rails
Run Rails whenever with docker
[Docker] Rails 5.2 environment construction with docker
Use multiple databases with Rails 6.0
[Rails] Specify format with link_to
Login function implementation with rails
[Docker] Use whenever with Docker + Rails
How to test a private method with RSpec for yourself