Nice to meet you. First of all, I would like to briefly introduce myself. From May 2020 to September 2020, I am a graduate who studied mainly Ruby at DMM WEB CAMP and is currently changing jobs. The other day, a study session for beginners [(RSpec Beginners !!)](https://www.youtube.com/watch?v=4lgaIeKdrR4 "" was held with the kindness of RSpec male Junichi Ito @jnchito. We also participated in RSpec Beginners !! ").
I felt while writing a test using RSpec in my portfolio, If you are a beginner like me, I have summarized the "stumbling points" such as "I'm going to stumble here: thinking:" and "I don't know here: hugging:" from the perspective of a beginner. Also, as I went along, I sometimes wanted a concrete test code example of the function I wanted to write, so I also serve as a review of myself, but if this article is a little helpful for RSpec beginners I'm happy.
** Target **
I'm trying to write RSpec, but I don't know what it is. A beginner who says: hugging :.
However, Mr. Ito's [Introduction to RSpec that can be used, Part 1 "Understanding the basic syntax and useful functions of RSpec"](https://qiita.com/jnchito/items/42193d066bd61c740612 "Introduction to RSpec that can be used, Part 1" Understand the basic syntax and useful features of RSpec "")
It is desirable to understand the role of describe, it, expect at least, such as looking at the contents of this article to some extent or having done something.
** Reference code ** As mentioned above, I will describe it with reference to my portfolio, so I will put a GitHub link here, but the test target is the site Please note that we are focusing on core functions. ** [Core functions of the site] ** (New registration / login / editing of individual / corporate members, application for corporate member registration, posting / editing of articles, DM, notification, etc.) DM, notifications, etc. will be introduced in the next SystemSpec edition.
** Preparation for writing tests ** In order to write a test with RSpec, you need to put in and configure some gems, including gem'rspec-rails'. Please prepare to write the test first and then read it. What you need is described in "Everyday Rails-Introduction to Rails Testing with RSpec". Or rather, if you look at this, you can understand it without looking at this article. If you want to see the code as a concrete example, please read it as it is. (It's really just for reference)
First of all, I will show you the individual user model. Actually, there are associations such as favorite models, but since we will not deal with them this time, we have deliberately deleted them.
①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
has_many :rooms, dependent: :destroy
has_many :messages, dependent: :destroy
has_many :notifications, dependent: :destroy
validates :last_name, presence: true, length: { maximum: 10 }
validates :first_name, presence: true, length: { maximum: 10 }
validates :kana_last_name, presence: true, length: { maximum: 10 }
validates :kana_first_name, presence: true, length: { maximum: 10 }
validates :email, presence: true, length: { maximum: 30 }
validates :postal_code, presence: true, length: { minimum: 7, maximum: 7 }
validates :address, presence: true, length: { maximum: 30 }
validates :phone_number, presence: true, length: { maximum: 12 }
validates :introduction, length: { maximum: 200 }
attachment :profile_image
def full_name
last_name + " " + first_name
end
def kana_full_name
kana_last_name + " " + kana_first_name
end
end
** Stumble point: hugging: "What is Factory Bot?" ** FactoryBot is also described in "Everyday Rails-Introduction to Rails Testing with RSpec". You can use it by inserting a gem, so you may try searching with FactoryBot. I didn't know at first, but it's okay. It's just a convenient thing that you can simply describe the test data and assign it to an instance variable or local variable such as @user in ③. How to use is described in ③. First, run $ bin / rails g factory_bot: model user to create a file, so when it is created, try inserting sample data according to the columns of your application.
②spec/factories/users.rb
FactoryBot.define do
#Use FactoryBot and prepare user data in advance
factory :user do
last_name { "test" }
first_name { "Taro" }
kana_last_name { "test" }
kana_first_name { "Taro" }
email { "[email protected]" }
postal_code { "1234567" }
address { "123, Chiyoda-ku, Tokyo-12-1" }
phone_number { "12345678910" }
password { "testtaro" }
end
end
I will finally write a test. rest assured. I will stumble properly later. Running $ bin / rails g rspec: model user will create user_spec.rb in the spec / models folder. Model test code (validation, self-created methods, etc.) will be written in this file.
Below is a completed example.
③spec/models/user_spec.rb
require 'rails_helper'
RSpec.describe User, type: :model do
before do
@user = FactoryBot.build(:user)
end
describe "Validation test" do
it "Last name, first name, Kana surname, Kana first name, email, zip code, address, phone number, password must be valid" do
expect(@user).to be_valid
end
it "Must be invalid without surname" do
@user.last_name = ""
@user.valid?
expect(@user.errors[:last_name]).to include("Please enter")
end
it "Must be invalid unless the surname is 10 characters or less" do
@user.last_name = "a" * 11
@user.valid?
expect(@user.errors[:last_name]).to include("Please enter within 10 characters")
end
it "If there is no name, it is in an invalid state" do
@user.first_name = ""
@user.valid?
expect(@user.errors[:first_name]).to include("Please enter")
end
it "The name must be 10 characters or less and must be invalid" do
@user.first_name = "a" * 11
@user.valid?
expect(@user.errors[:first_name]).to include("Please enter within 10 characters")
end
it "Must be invalid without Kana surname" do
@user.kana_last_name = ""
@user.valid?
expect(@user.errors[:kana_last_name]).to include("Please enter")
end
it "Kana Last name must be 10 characters or less to be invalid" do
@user.kana_last_name = "a" * 11
@user.valid?
expect(@user.errors[:kana_last_name]).to include("Please enter within 10 characters")
end
it "If there is no kana name, it is in an invalid state" do
@user.kana_first_name = ""
@user.valid?
expect(@user.errors[:kana_first_name]).to include("Please enter")
end
it "The kana name must be 10 characters or less to be invalid" do
@user.kana_first_name = "a" * 11
@user.valid?
expect(@user.errors[:kana_first_name]).to include("Please enter within 10 characters")
end
it "If there is no email address, it is invalid" do
@user.email = ""
@user.valid?
expect(@user.errors[:email]).to include("Please enter")
end
it "The email address must be 30 characters or less to be invalid" do
@user.email = "a" * 31
@user.valid?
expect(@user.errors[:email]).to include("Please enter 30 characters or less.")
end
it "Must be invalid without zip code" do
@user.postal_code = ""
@user.valid?
expect(@user.errors[:postal_code]).to include("Please enter")
end
it "If the zip code is less than 7 characters, it is invalid" do
@user.postal_code = "a" * 6
@user.valid?
expect(@user.errors[:postal_code]).to include("Please enter at least 7 characters")
end
it "If the zip code exceeds 7 characters, it is invalid." do
@user.postal_code = "a" * 8
@user.valid?
expect(@user.errors[:postal_code]).to include("Please enter within 7 characters")
end
it "Must be invalid without an address" do
@user.address = ""
@user.valid?
expect(@user.errors[:address]).to include("Please enter")
end
it "The address must be 30 characters or less to be invalid" do
@user.address = "a" * 31
@user.valid?
expect(@user.errors[:address]).to include("Please enter within 30 characters")
end
it "If there is no phone number, it is invalid" do
@user.phone_number = ""
@user.valid?
expect(@user.errors[:phone_number]).to include("Please enter")
end
it "The phone number must be 12 characters or less to be invalid" do
@user.phone_number = "a" * 13
@user.valid?
expect(@user.errors[:phone_number]).to include("Please enter within 12 characters")
end
it "The self-introduction text must be 200 characters or less to be invalid." do
@user.introduction = "a" * 201
@user.valid?
expect(@user.errors[:introduction]).to include("Please enter within 200 characters")
end
it "The password must be 6 characters or more and is invalid" do
@user.password = "a" * 5
@user.valid?
expect(@user.errors[:password]).to include("Please enter at least 6 characters.")
end
it "If the email address is duplicated, it must be invalid." do
FactoryBot.create(:user)
@user.valid?
expect(@user.errors[:email]).to include("Already exists.")
end
end
describe "Instance method testing" do
it "Returning the user's full name as a string" do
@user.last_name = "test"
@user.first_name = "Taro"
expect(@user.full_name).to eq "Test Taro"
end
it "Returning the user's kana full name as a string" do
@user.kana_last_name = "test"
@user.kana_first_name = "Taro"
expect(@user.kana_full_name).to eq "Test Taro"
end
end
end
"Hmmmm, I'll write the test code in this file. OK: vulcan: I don't really understand from the first line! ** require'rails_helper' ** What's that: hugging:" Those who thought. This tells RSpec that the Rails application needs to be loaded to run the tests in the file. This description is required for almost every file in the test suite. (Quoted from Everyday Rails) I'm starting to think that I don't need to explain anything about Everyday Rails, but I'll do my best.
In short, by writing require'rails_helper', for RSpec seniors ** "My application looks like this! The model has this data, the controller describes it like this, and the view shows this! Please understand!" ** I am telling you that. Thanks to this description, RSpec will test your application against the code you write.
So what is that rails_helper? Those who thought. Rspec settings can be written in ** rails_helper.rb ** in the spec folder. The setting to enable the devise helper method in system_spec, which will be used in the system specs scheduled to be described next time, is also written here.
The part of this setting etc. is also described in Everyday Rails. I'm really relying on Everyday Rails, but I recommend it because it's a book that covers specific descriptions of test code and why this description is necessary.
spec/rails_helper.rb
#devise helper method system_Make available in spec
config.include Devise::Test::IntegrationHelpers, type: :system
See the code example below. First, the before block is used to make the code DRY when the same data is required for multiple tests in the same file. In this case, the test data of spec / factories / users.rb described in (2) above is built and assigned to @user by the description of FactoryBot.build (: user) on the right side. By doing so, @user can be used in later it blocks etc. Overwrite the last_name data with empty @ user.last_name = "" on the first line of the it block, such as "If @ user.last_name is nil, it is invalid ~" or @ user.last_name = "a" * In 11, I overwrote the data "aaaaaaaaaaaa" to last_name and wrote a test such as "last_name is validated, so it is invalid unless it is 10 characters or less." By the way, the be_valid in front of it is called Matcher. Regarding matcha, Mr. Ito's [Introduction to RSpec that can be used, part 2 "Mastering frequently used matcha" ](Https://qiita.com/jnchito/items/2e79a1abe7cd8214caa5 "Introduction to RSpec that can be used, part 2" Mastering frequently used matchers " I think you should refer to ").
** Stumble point: hugging: "Factory Bot description position" ** Also, by placing a before block directly under RSpec.describe User, type:: model do and describing @user created by FactoryBot in it, @user in the before block is all in the same file. It can be used in tests. This time it's still a simple association, so it's better, but as the number of associations increases, more and more data is created with FactoryBot, and I'm addicted to the position to describe at that time. Especially System Spec. It was a swamp. I will talk about it later.
③spec/models/user_spec.rb
RSpec.describe User, type: :model do
#The before block is used to make the code DRY when the same data is required for multiple tests in the same file.
before do
@user = FactoryBot.build(:user) # @last for user_name{ "test" }、first_name { "Taro" }Contains data such as
end
it "Last name, first name, Kana surname, Kana first name, email, zip code, address, phone number, password must be valid" do
expect(@user).to be_valid
end
it "Must be invalid without surname" do
@user.last_name = "" # @user.last_If the name is nil, it's invalid ~
@user.valid?
expect(@user.errors[:last_name]).to include("Please enter")
end
it "Must be invalid unless the surname is 10 characters or less" do
@user.last_name = "a" * 11 # last_Since name is being validated, it is invalid unless it is 10 characters or less ~
@user.valid?
expect(@user.errors[:last_name]).to include("Please enter within 10 characters")
end
③spec/models/user_spec.rb
it "If the email address is duplicated, it must be invalid." do
FactoryBot.create(:user) #Saved first
@user.valid?
expect(@user.errors[:email]).to include("Already exists.")
end
The rest of the it block has the same structure, and I think it's understandable somehow, so I'll fold it. Please note that the error statement for the include (please enter "") part of expect differs from person to person. If you have translated the error by i18n into Japanese, check it with devise.ja.yml etc., or check the error text of the terminal at runtime, so check each and enter the wording.
③spec/models/user_spec.rb
describe "Instance method testing" do
it "Returning the user's full name as a string" do
@user.last_name = "test"
@user.first_name = "Taro"
expect(@user.full_name).to eq "Test Taro"
end
it "Returning the user's kana full name as a string" do
@user.kana_last_name = "test"
@user.kana_first_name = "Taro"
expect(@user.kana_full_name).to eq "Test Taro"
end
end
Well, it's easy enough to understand without any explanation, so I won't explain it. It's just that the full_name method and kana_full_name method described in the model of ① are properly full names. As for method testing, I can't write anything complicated, so I'll study it. ..
Actually, there are associations such as Relationship model, but we will not deal with it this time, so we have deleted it. Since the test for corporate users is almost the same as for individual users, only the code is described, so please refer to the following.
①company.rb
class Company < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
has_many :rooms, dependent: :destroy
has_many :messages, dependent: :destroy
has_many :articles, dependent: :destroy
has_many :notifications, dependent: :destroy
validates :company_name, presence: true, length: { maximum: 30 }
validates :kana_company_name, presence: true, length: { maximum: 30 }
validates :email, presence: true, length: { maximum: 30 }
validates :postal_code, presence: true, length: { minimum: 7, maximum: 7 }
validates :address, presence: true, length: { maximum: 30 }
validates :phone_number, presence: true, length: { maximum: 12 }
validates :introduction, length: { maximum: 800 }
attachment :profile_image
attachment :background_image
#If approved is true, you can log in. At the time of new registration, default is false, so you cannot log in.
def active_for_authentication?
super && self.approved?
end
#The message after the login is flipped above. For wording details, see config/locales/devise.ja.Described in yml.
def inactive_message
self.approved? ? super : :needs_admin_approval
end
def followed_by?(user)
passive_relationships.find_by(following_id: user.id).present?
end
end
②spec/factories/companies.rb
FactoryBot.define do
factory :company do
company_name { "Test Co., Ltd." }
kana_company_name { "Test Co., Ltd." }
email { "[email protected]" }
postal_code { "1234567" }
address { "123, Chiyoda-ku, Tokyo-12-1" }
phone_number { "12345678910" }
password { "testcompany" }
approved { true }
is_active { true }
end
end
③spec/models/company_spec.rb
require 'rails_helper'
RSpec.describe Company, type: :model do
describe "Validation test" do
before do
@company = FactoryBot.build(:company)
end
it "If you have the company name, company kana name, email, zip code, address, phone number, and password, they must be valid." do
expect(@company).to be_valid
end
it "It is invalid without the company name" do
@company.company_name = ""
@company.valid?
expect(@company.errors[:company_name]).to include("Please enter")
end
it "It is invalid if there is no company kana name" do
@company.kana_company_name = ""
@company.valid?
expect(@company.errors[:kana_company_name]).to include("Please enter")
end
it "If there is no email address, it is invalid" do
@company.email = ""
@company.valid?
expect(@company.errors[:email]).to include("Please enter")
end
it "Must be invalid without zip code" do
@company.postal_code = ""
@company.valid?
expect(@company.errors[:postal_code]).to include("Please enter")
end
it "Must be invalid without an address" do
@company.address = ""
@company.valid?
expect(@company.errors[:address]).to include("Please enter")
end
it "If there is no phone number, it is invalid" do
@company.phone_number = ""
@company.valid?
expect(@company.errors[:phone_number]).to include("Please enter")
end
it "The password must be 6 characters or more and is invalid" do
@company.password = "a" * 5
@company.valid?
expect(@company.errors[:password]).to include("Please enter at least 6 characters")
end
it "If the email address is duplicated, it must be invalid." do
FactoryBot.create(:company)
@company.valid?
expect(@company.errors[:email]).to include("Already exists")
end
end
end
The genre is not particularly difficult, so I will omit the explanation.
①genre.rb
class Genre < ApplicationRecord
has_many :articles, dependent: :destroy
validates :genre_name, presence: true, length: { maximum: 15 }
end
②spec/factories/genres.rb
FactoryBot.define do
factory :genre do
genre_name { "Test genre" }
is_active { true }
end
end
③spec/models/genre_spec.rb
require 'rails_helper'
RSpec.describe Genre, type: :model do
describe "Validation test" do
it "If there is no genre name, it is invalid" do
@genre = FactoryBot.build(:genre)
@genre.genre_name = ""
@genre.valid?
expect(@genre.errors[:genre_name]).to include("Please enter")
end
end
end
I'm finally here. The model specs in the article are slightly different, so I would like to explain them briefly. Below is the article model.
①article.rb
class Article < ApplicationRecord
belongs_to :company
belongs_to :genre
validates :title, presence: true, length: { maximum: 35 }
validates :body, presence: true
attachment :image
#Search only for articles with valid publication status and genre enabled
def self.all_active
where(is_active: true).joins(:genre).where(genres: {is_active: true})
end
def favorited_by?(user)
favorites.where(user_id: user.id).exists?
end
end
②spec/factories/articles.rb
FactoryBot.define do
factory :article do
title { "Test title" }
body { "Test body" }
is_active { true }
company
genre
end
end
I will write a test of the article model. $ bin / rails g rspec: model article will create article_spec.rb in the spec / models folder. Below is an example of the completed code.
③spec/models/article_spec.rb
require 'rails_helper'
RSpec.describe Article, type: :model do
describe "Article test" do
before do
@company = FactoryBot.create(:company)
@genre = FactoryBot.create(:genre)
@article = FactoryBot.build(:article)
end
#article creation
context "When all data is included" do
it "All are entered so it will be saved" do
@article.company_id = @company.id
@article.genre_id = @genre.id
expect(@article.save).to be true
end
end
context "When not all data is included" do
it "Not saved because not all have been entered" do
@article.company_id = @company.id
@article.genre_id = @genre.id
@article.title = ""
@article.body = ""
expect(@article.save).to be false
end
end
end
describe "Validation test" do
before do
@article = FactoryBot.build(:article)
end
context "If the title does not exist" do
it "Being in an invalid state" do
@article.title = ""
@article.valid?
expect(@article.errors[:title]).to include("Please enter")
end
end
context "If the title exceeds 35 characters" do
it "I get an error message" do
@article.title = "a" * 36
@article.valid?
expect(@article.errors[:title]).to include("Please enter within 35 characters")
end
end
it "If there is no text, it is invalid" do
@article.body = ""
@article.valid?
expect(@article.errors[:body]).to include("Please enter")
end
end
end
Well, there is nothing new so far, but the article model is as described in ① belongs_to :company belongs_to :genre It is an association like that, and it is a little different from before. As shown below, in the before block, company and genre are first created with FactoryBot, saved in the database, and then assigned to @company and @genre, respectively. The flow of article posting There is a company → Select an article genre → You can post an article It has become a flow. Based on this, since ** corporation and genre must exist ** to post an article, the before block is described as follows.
The rest is easy.
③spec/models/article_spec.rb
RSpec.describe Article, type: :model do
describe "Article test" do
before do
#There is a company → Select an article genre → You can post an article
@company = FactoryBot.create(:company)
@genre = FactoryBot.create(:genre)
@article = FactoryBot.build(:article)
end
#article creation
context "When all data is included" do
it "All are entered so it will be saved" do
@article.company_id = @company.id
@article.genre_id = @genre.id
expect(@article.save).to be true
end
end
context "When not all data is included" do
it "Not saved because not all have been entered" do
@article.company_id = @company.id
@article.genre_id = @genre.id
@article.title = ""
@article.body = ""
expect(@article.save).to be false
end
end
end
This completes the model test! There is also a model spec of the message model, but I will omit it because the number of items is small. If you are interested, please see from GitHub.
This time, we have summarized the model testing and model specifications, which are the core parts of the application. Model specs are also important, but the most important ones are the system specs described next time. Test the behavior in a real browser, such as a user editing My Page or sending a DM to a corporation. There were many points to stumble. Maybe I'm the only one: thinking: I'd like to summarize it in an easy-to-understand manner, so I hope you'll see it again next time. Thank you for watching until the end.
** 2020.09.28 System specs edition has been posted. Please see here. ** **
https://qiita.com/jnchito/items/2a5d3e15761fd413657a https://qiita.com/jnchito/items/42193d066bd61c740612 https://qiita.com/jnchito/items/2e79a1abe7cd8214caa5
Recommended Posts