FactoryBot - thuy-econsys/rails_app GitHub Wiki
Setup
In the Gemfile:
group :development, :test do
gem "factory_bot_rails"
end
Include FactoryBot::Syntax::Methods
to facilitate the simpler invocation of FactoryBot methods without having to prepend FactoryBot.
For example, create()
instead of FactoryBot.create()
. Either in /spec/rails_helper
or a dedicated /spec/support/factory_bot.rb
file, within the RSpec.configure
block:
RSpec.configure do |config|
config.include FactoryBot::Syntax::Methods
end
With a separate setup /spec/support/factory_bot.rb
, there needs to be a require in the /spec/rails_helper.rb
:
require 'support/factory_bot'
Define factories
Make sure to define factories either in factory file /spec/factories.rb
or in a factories directory /spec/factories/*.rb
.
FactoryBot.define do
factory :user do
email { "[email protected]" }
password { "password" }
end
end
Invoking create(:user)
creates a User instance that can be tested against. Adding attributes as options overrides the original attributes defined in the factory: create(:user, password: 'notthatone')
.
To see what attributes an object has, use the attributes_for
method by passing the object as an argument. It will return a hash of the attributes, so it's possible to access the value of a specific attribute with square brackets:
attributes_for(:user) # { :email => "[email protected]", :password => "notthatone" }
attributes_for(:user)[:email] # "[email protected]"
Use attributes_for
with let
to generate a hash of valid attributes for your specs:
let(:valid_attributes) { attributes_for(:user) }
...
context "for POST #create" do
it "creates a new user" do
expect {
post :create, params: {
:user => valid_attributes
}
}.to change{ User.count }.by(1)
end
it "redirects to user path, show action" do
post :create, params: {
:user => valid_attributes
}
expect(response).to have_http_status(302)
expect(response).to redirect_to(user_url(assigns(:user)))
expect(response).to redirect_to(:action => :show, :id => assigns(:user).id)
end
end
...
Instead of an instance variable use the lazy let
to generate a user object for testing as it will take up less resources:
let(:test_user) { create(:user) }
...
context "for GET #edit" do
it "returns a 200 OK status" do
get :edit, params: {
:id => test_user.to_param
}
expect(response).to have_http_status(200)
expect(response).to have_http_status(:ok)
end
it "renders an 'edit' template" do
get :edit, params: {
:id => test_user.to_param
}
expect(response).to render_template(:edit)
end
end
...
trait
setup different users using FactoryBot One place to override attributes but also maintain the original is within the factory utilizing trait
.
factory :user do
email { "[email protected]" }
password {"Password1!"}
password_confirmation {"#{password}"}
account_type {0}
# attributes for child user overriding parent traits
trait :admin do
email { "[email protected]" }
password {"Password2!"}
account_type {1}
end
trait :moderator do
email { "[email protected]" }
password {"Password3!"}
account_type {2}
end
# register child factories with their overriding traits
factory :admin, traits: [:admin]
factory :moderator, traits: [:moderator]
end
end
build
vs create
FactoryBot's build()
is instantiating an object, similar to Ruby's .new
. create()
instantiates and saves the object to the database, similar to .new
followed by a .save
which is essentially a .create
. Keep this in mind if you ever get rspec error:
ActiveRecord::RecordInvalid:
Validation failed: Email has already been taken
The User model has uniqueness validation for the email attribute. If the value is hard-coded in the factory file, the tests will not even run with the invocation of create(:user)
. One way to get around the error is to use sequence
to increment the FactoryBot object where n
is the integer incrementing. Another is to utilize Faker's unique
method.
# /spec/factories.rb
FactoryBot.define do
factory :user do
name { Faker::Name.unique.name }
sequence(:email) { |n| "#{name.parameterize}-#{n}@example.com" } # [email protected], [email protected], [email protected]...
end
end
The following resets the sequence between each and every test. Not sure if it's needed as running examples as transactions starts each example with a clean database:
# ./spec/rails_helper.rb
RSpec.configure do |config|
config.after do
FactoryBot.rewind_sequences
end
end
- when does factory girl create objects in db?
- Why does my test fail when I use FactoryGirl in this manner? | StackOverflow
- Deterministic test data with Faker, FactoryBot, and RSpec