A reader of mine recently asked me how I set up Factory Bot for a new Rails project.
There are four steps I go through to set up Factory Bot.
- Install the
factory_bot_rails
gem - Set up one or more factory definitions
- Install Faker
- Add the Factory Bot syntax methods to my
rails_helper.rb
file
Following are the details for each step.
Install the factory_bot_rails gem
The first thing I do is to include the factory_bot_rails gem (not the factory_bot
gem) in my Gemfile
. I include it under the :development, :test
group.
Here’s a sample Gemfile from a project with only the default gems plus a few that I added for testing.
Remember that after you add a gem to your Gemfile
you’ll need to run bundle install
in order to actually install the gem.
source 'https://rubygems.org'
git_source(:github) { |repo| "https://github.com/#{repo}.git" }
ruby '2.7.0'
# Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
gem 'rails', '~> 6.0.2', '>= 6.0.2.2'
# Use postgresql as the database for Active Record
gem 'pg', '>= 0.18', '< 2.0'
# Use Puma as the app server
gem 'puma', '~> 4.1'
# Use SCSS for stylesheets
gem 'sass-rails', '>= 6'
# Transpile app-like JavaScript. Read more: https://github.com/rails/webpacker
gem 'webpacker', '~> 4.0'
# Turbolinks makes navigating your web application faster. Read more: https://github.com/turbolinks/turbolinks
gem 'turbolinks', '~> 5'
# Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder
gem 'devise'
# Reduces boot times through caching; required in config/boot.rb
gem 'bootsnap', '>= 1.4.2', require: false
group :development, :test do
gem 'pry'
gem 'rspec-rails'
gem 'capybara'
gem 'webdrivers'
gem 'factory_bot_rails'
end
group :development do
# Access an interactive console on exception pages or by calling 'console' anywhere in the code.
gem 'web-console', '>= 3.3.0'
gem 'listen', '>= 3.0.5', '< 3.2'
# Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring
gem 'spring'
gem 'spring-watcher-listen', '~> 2.0.0'
end
# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]
Set up one or more factory definitions
Factory definitions are kind of the “templates” that are used for generating new objects.
For example, I have a user object that needs an email and a password, then I would create a factory definition saying “hey, make me a user with an email and password”. The actual code might look like this:
FactoryBot.define do
factory :user do
email { 'test@example.com' }
password { 'password1' }
end
end
Factory Bot is smart enough to know that when I say factory :user do
, I’m talking about an Active Record class called User
.
There’s a problem with this way of defining my User
factory though. If I have a unique constraint on the users.email
column in the database (for example), then I won’t ever be able to generate more than one User
object. The first user’s email address will be test@example.com
(no problem so far) but then when I go to create a second user, its email address will also be test@example.com
, and if I have a unique constraint on users.email
, the creation of this second record will not be allowed.
We need a way of making it so the factories’ values can be unique. One way, which I’ve done before, is to append a random number to the end of the email address, e.g. "test#{SecureRandom.hex}@example.com"
. There’s a different way to do it, though, that I find nicer. That way is to use another gem called Faker.
Install Faker
Just like I showed with factory_bot_rails
above, the Faker gem can be added by putting it into the :development, :test
group of the Gemfile
.
Then we can change our User
factory definition as follows.
FactoryBot.define do
factory :user do
email { Faker::Internet.email }
password { Faker::Internet.password }
end
end
This will give us random values like eldora@jones.net
and lazaromertz@ko.name
.
Add the Factory Bot syntax methods to my rails_helper.rb file
The syntax for actually using a Factory Bot factory in a test is as follows:
FactoryBot.create(:user)
There’s nothing wrong with this, but I find that these FactoryBot
are so numerous in my test files that their presence feels a little noisy.
There’s a way to make it so that instead we can just write this:
create(:user)
The way to do that is to add a bit of code to spec/rails_helper.rb
.
RSpec.configure do |config|
config.include FactoryBot::Syntax::Methods
end
(You don’t actually add the RSpec.configure do |config|
to the spec/rails_helper.rb
file. It’s already there. I’m just including it here to show that that’s the block inside of which the config.include FactoryBot::Syntax::Methods
line goes.)
What to do next
If you’re curious how to put Factory Bot together with the other testing tools to write some complete Rails tests, I might suggest my Rails testing “hello world” tutorial using RSpec and Capybara.
Hi, Jason!
This blog post is handy. I wish I had something like this way back when I started testing. At Monday.vc, we are using the FactoryBot + Faker for our specs, and I would like to comment on something we did experience some time ago.
We you are working with uniqueness constraints, if you create several models, like the User model in your example, and you rely on the Random function of Faker, it is just a matter of time that you encounter the same string for while running the test suite.
I’m new to you your blog, and loving it already, and I thought it was worth mentioning. I couldn’t find a blog post for this, but maybe you already had one.
Greetings!
Thanks for the kind words and the tip regarding Faker. I don’t believe I’ve experienced that myself yet although I can imagine how that could happen.
Nice post, Jason!
I have also run into some test flakiness in the past when using Faker. I like to use factory_bot sequences to handle attributes that need to be unique: https://github.com/thoughtbot/factory_bot/blob/master/GETTING_STARTED.md#inline-sequences
Thanks!
You can use the unique wrapper with Faker to ensure unique results:
https://github.com/faker-ruby/faker#ensuring-unique-values
However, if you have a large test suite (or a small pool of Faker strings) you may encounter an error where you use all the possible variations.
For the factory definitions starting with “FactoryBot.define” – what path/file do they go in? Is it one file per model?
They can go in
spec/factories/*.rb
and either go in one file or multiple files. Here’s the relevant part of the docs.