Deterministic test data with Faker, FactoryBot, and RSpec
I get a lot of joy from using Faker and FactoryBot to efficiently generate real-world test data, but its randomness can be a liability when trying to debug complicated specs or when setting up systems that require repeatable data across RSpec test runs like Percy’s visual diffs.
Without deterministic test data, generating three new users with 3.times { puts Faker::Name.first_name }
would result in Danny, Solomon, Fabian
when run once, then Jordon, Shawn, Asa
when run a second time, then Bruce, Leonor, Paulette
when run a third time.
With deterministic test data, I expect to always generate the same set of names no matter how many times the code is run. Faker has documented how to configure and seed the random number generator and this can be achieved with:
3.times do |n|
Faker::Config.random = Random.new(n)
puts Faker::Name.first_name
end
This script outputs Zachery, Dawna, Desmond
every single time it is run, meaning that it’s deterministic.
Faker’s deterministic configuration can be combined with a FactoryBot sequence
to always get the same data every time a new factory instance is created. For example, here’s what a deterministic User
factory could look like:
# spec/factories/users.rb
FactoryBot.define do
factory :user do
sequence(:first_name) do |n|
Faker::Config.random = Random.new(n)
Faker::Name.first_name
end
sequence(:last_name) do |n|
Faker::Config.random = Random.new(n)
Faker::Name.last_name
end
email { "#{first_name.parameterize}.#{last_name.parameterize}@example.com" }
password { 'password123' }
end
end
Within every sequence, the Faker random number generator is seeded with Faker::Config.random = Random.new(n)
, where n
is the integer generated by the sequence.
Unfortunately, just using a sequence isn’t completely sufficient when running tests in random order, or inserting new tests or rearranging the tests, as one would expect in an active codebase. FactoryBot sequences are global, meaning that they don’t reset by default between each and every test; a FactoryBot instance during one test run might use a different sequence number than a previous test run.
Therefore, it’s also necessary to rewind FactoryBot sequences after each RSpec example. Place this in your spec/rails_helper.rb
or spec/support
directory:
# spec/support/factory_bot.rb
RSpec.configure do |config|
config.after do
FactoryBot.rewind_sequences
end
end
That’s all you need to combine Faker and FactoryBot to get deterministic test data in your RSpec tests. Have fun!