Last time continued
I want to keep the test data setup simple even if the test becomes complicated.
Gem's ** Factory Bot ** is a well-known Ruby library that can be used in such cases, so I will summarize how to use it and notes.
As a means of generating sample data in Rails, a function called fixture is provided by default as a file in YAML format. Fixtures are a great feature, but there are some things to keep in mind that Rails doesn't use Active Record to load fixture data into the database, so it behaves differently in production.
The factory can easily generate test data and make the code short and easy to read, but if you are not aware of the generated data, unexpected data will be generated during the test and test execution will be useless It will be late.
If you understand the characteristics of each and use them properly, you will be able to write tests more smoothly.
Gemfile
group :development, :test do
gem "factory_bot_rails"
end
$ bundle install
By the way, when you generate a model with rails generate, we will set the factory to be generated automatically.
Just remove fixtures: false.
config/application.rb
config.generators do |g|
g.test_framework :rspec,
view_specs: false
helper_specs: false,
routing_specs: false
end
Now that you have the Factory Bot installed, let's add a factory for the User model.
$ rails g factory_bot:model user
When you run this command, a new directory called factories will be created in the spec directory, and a file named users.rb will be created in it with the following contents.
spec/factories/users.rb
FactoryBot.define do
factory :user do
end
end
I will create test data here.
spec/factories/users.rb
FactoryBot.define do
factory :user do
name { "Zeisho" }
email { "[email protected]" }
password { "hogehoge" }
end
end
Let's check user_spec.rb to see if the test data is created properly.
spec/models/user_spec.rb
require 'rails_helper'
describe User do
#Have a valid factory
it "has a valid factory" do
expect(FactoryBot.build(:user)).to be_valid
end
#Other specs
end
Here we are using FactoryBot.build to instantiate the user and test its effectiveness. It has more compact specifications than the valid user instance test described in the previous article.
Overwrite the FactoryBot data and rewrite the validation error test.
spec/models/user_spec.rb
require 'rails_helper'
describe User do
#Have a valid factory
it "has a valid factory" do
expect(FactoryBot.build(:user)).to be_valid
end
#Must be invalid without name
it "is invalid without a name"
user = FactoryBot.build(:user, name: nil)
user.valid?
expect(user.errors[:name]).to include("can't be blank")
end
#If there is no email address, it is invalid
it "is invalid without a email"
user = FactoryBot.build(:user, email: nil)
user.valid?
expect(user.errors[:email]).to include("can't be blank")
end
#Must be invalid without password
it "is invalid without a password"
user = FactoryBot.build(:user, password: nil)
user.valid?
expect(user.errors[:password]).to include("can't be blank")
end
#If the email address is duplicated, it must be invalid.
it "is invalid with a duplicate email address" do
FactoryBot.create(:user, email: "[email protected]")
user = FactoryBot.build(:user, email: "[email protected]")
user.valid?
expect(user.errors[:email]).to include("has already been taken")
end
end
When generating multiple test data in example
FactoryBot.create(:user)
If you use repeatedly, the attributes such as name and email will be exactly the same, and the test execution may stop due to a validation error.
Factory Bot can use sequences to generate and resolve data with unique validation.
spec/factories/users.rb
FactoryBot.define do
factory :user do
name { "Zeisho" }
sequence(:email) { |n| "hoge#{n}@hoge.com" }
password { "hogehoge" }
end
end
By doing the above, you can have a unique email address each time the factory creates a new user.
FactoryBot can also generate data that is conscious of the association (association) of multiple models, so I will introduce it.
When associating, for example, when you want to create data of Note model belonging to User model and Project model, if you create an instance of Note model, the data of User and Project model associated with it will be automatically generated. Will come to you.
First, create data for the Note model that belongs to the User model and Project model.
$ rails g factory_bot:model note
spec/factories/notes.rb
FactoryBot.define do
factory :note do
message { "My important note." }
association :project #Association with test data project
user { project.owner } #Association with test data user
end
end
Next is the Project model, which belongs to the User model and owns the Note model.
$ rails g factory_bot:model project
spec/factories/projects.rb
FactoryBot.define do
factory :project do
sequence(:name) { |n| "Project #{n}" }
description { "A test project." }
due_on {1.week.from_now}
association :owner #Association of the owner
end
end
Finally, add the association to User and you're done.
spec/factories/users.rb
FactoryBot.define do
factory :user, aliases: [:owner] do
name { "Zeisho" }
sequence(:email) { |n| "hoge#{n}@hoge.com" }
password { "hogehoge" }
end
end
With FactoryBot, you can scoop multiple data in one file, and duplicate attributes can be omitted.
Let's take the case of giving a user multiple projects as an example.
spec/factories/projects.rb
FactoryBot.define do
factory :project do
sequence(:name) { |n| "Project #{n}" }
description { "A test project." }
due_on {1.week.from_now}
association :owner
#Project deadline yesterday
factory :project_due_yesterday do
due_on { 1.day.ago }
end
#Today is the deadline project
factory :project_due_today do
due_on { Date.current.in_time_zone }
end
#Project deadline tomorrow
factory :project_due_tomorrow do
due_on { 1.day.from_now }
end
end
end
By writing in the block of: project, you can generate data that inherits attributes other than due_on from: project. When calling the factory data in the test, you can call it by specifying the factory name as it is.
Also, since FactoryBot is nested, it can be determined that: project_due_yesterday,: project_due_today,: project_due_tomorrow is a child factory of: project, so ** trait ** can be used to eliminate the designation of class: Project. I can do it.
spec/factories/projects.rb
FactoryBot.define do
factory :project do
sequence(:name) { |n| "Project #{n}" }
description { "A test project." }
due_on {1.week.from_now}
association :owner
#Project deadline yesterday
trait :due_yesterday do
due_on { 1.day.ago }
end
#Today is the deadline project
trait :due_today do
due_on { Date.current.in_time_zone }
end
#Project deadline tomorrow
trait :due_tomorrow do
due_on { 1.day.from_now }
end
end
end
To call a child factory with a trait,
FactoryBot.create(:project, :due_yesterday)
It can be called by setting ** parent factory, child factory ** like.
Callbacks allow you to do additional work before and after the factory creates, builds, etc. an object.
When creating a project object, let's define a callback that also generates a note associated with it.
spec/factories/projects.rb
FactoryBot.define do
factory :project do
sequence(:name) { |n| "Project #{n}" }
description { "A test project." }
due_on {1.week.from_now}
association :owner
#Project with notes
trait :with_notes do
after(:create) { |project| create_list(:note, 5, project: project) }
end
end
end
In: with_notes, after creating the project object, 5 note objects are created using the create_list method.
The defined callback can be called as follows.
FactoryBot.create(:project, :with_notes)
Recommended Posts