Jake Worth

Why Use FactoryBot?

Published: October 17, 2022 3 min read

  • ruby

The FactoryBot gem by Thoughtbot is ubiquitous in Ruby testing. If you aren’t familiar with it, you might be wondering, what’s the point? Wouldn’t it be simpler to build objects myself, with one less dependency in test?

In this post, I’ll explain what factories are and why you want them.

An Example Test

Let’s say we’re unit testing a full_name method on a User class. full_name takes two pieces of data, first_name and last_name, and combines them with a space. Let’s also assume that we’ve tried to ensure data integrity by requiring the presence of these two fields.

# app/models/users.rb

class User < ApplicationRecord
  validates :first_name, :last_name, presence: true

  def full_name
    "#{first_name} #{last_name}"
  end
end

Here’s our unit test:

# spec/models/user_spec.rb

RSpec.describe User do
  specify "#full_name returns a full name" do
    user = User.create!(first_name: "Jim", last_name: "Weirich")

    expect(user.full_name).to eq("Jim Weirich")
  end
end

Seems like we’re doing fine without a factory.

How Factories Help

However, our user class soon grows. Now we require three more pieces of data on the user: email, last IP address, and birth date.

# app/models/users.rb

class User < ApplicationRecord
  validates :first_name,
    :last_name,
    :email,
    :last_ip,
    :birth_date, presence: true
end

When we run our test after adding these fields, it fails, because we didn’t give our test user all the data that it now requires. Let’s do that.

# spec/models/user_spec.rb

RSpec.describe User do
  specify "#full_name returns a full name" do
    user = User.create!(
      first_name: "Jim",
      last_name: "Weirich",
      email: "jim@example.com",
      last_ip: "192.158.1.38",
      birth_date: Date.today
    )

    expect(user.full_name).to eq("Jim Weirich")
  end
end

Now the test passes– but is it a good test? Why did we include email, last_ip, and birth_date in the setup? Because the model requires them. What does that have to do with the test? Nothing. Which pieces of data make full_name equal “Jim Weirich”? We can guess, but we don’t really know.

Also, we must now add this data to every single test that previously created a user. That’s a lot of rework! And it’s going to surface again and again each time the model changes.

Adding behavior to the model means rewriting unrelated tests, and tests that explicitly list every required field aren’t very simple or maintainable. These two problems are why Rubyists use factories!

Wouldn’t it be nice to just say “If I have a user with the first name ‘Jim’ and last name ‘Weirich’, then and the full name is ‘Jim Weirich’”, with no other information? That’s what factories do.

Adding Factories

Let’s create a user factory with FactoryBot.

# spec/factories/users.rb

FactoryBot.define do
  factory :user do
    first_name: "Jim"
    last_name: "Weirich"
    email: "jim@example.com"
    last_ip: "192.158.1.38"
    birth_date: Date.today
  end
end

Here’s our new test. Look familiar?

# spec/models/user_spec.rb

RSpec.describe User do
  specify "#full_name returns a full name" do
    user = FactoryBot.create(first_name: "Jim", last_name: "Weirich")

    expect(user.full_name).to eq("Jim Weirich")
  end
end

Now, we don’t have to change this test when a validation is added, we only have to change the factory. The test strongly implies which pieces of data (first_name and last_name) matter toward the output of full_name. It’s a better test– more robust and communicative.

One last point: since our factory creates this specific first name and last name pairing, shouldn’t we omit them in the test, too? No, we shouldn’t. That data tell us why the test produces “Jim Weirich”. Without it, we’re just guessing about what the method does. And hard-coding them in the tests makes it harder for somebody else to inadvertently break the test by editing the factory.

✉️ Get better at programming by learning with me. Subscribe to Jake Worth's Newsletter for bi-weekly ideas, creations, and curated resources from across the world of programming. Join me today!


Blog of Jake Worth, software engineer in Maine.

© 2022 Jake Worth.