Back to Scaling Ruby Applications guides

FactoryBot for Test Data Management in Ruby

Stanley Ulili
Updated on October 7, 2025

FactoryBot is a fixtures replacement library that provides a flexible and maintainable way to set up test data for your Ruby applications. Originally created by thoughtbot as Factory Girl, it has become the de facto standard for managing test data in Ruby projects, particularly in Rails applications where complex object graphs and database relationships make traditional fixtures cumbersome to maintain.

FactoryBot includes all the features you need for sophisticated test data management: attribute sequencing, associations, traits for variations, callbacks for complex setup, and strategies for different object persistence needs. Its clean syntax and powerful features help you write tests that are both readable and maintainable, even as your application grows in complexity.

This tutorial will guide you through setting up and using FactoryBot in your Ruby projects.

Prerequisites

Before working through this tutorial, ensure you have Ruby (version 3.0 or higher recommended) installed on your system. You should have a basic understanding of Ruby syntax and testing concepts. This article assumes you're familiar with either RSpec or Minitest, and have some experience with ActiveRecord or another ORM.

Setting up your first FactoryBot project

To demonstrate FactoryBot's capabilities effectively, we'll create a simple Ruby project with ActiveRecord models. This approach works whether you're building a Rails application or a standalone Ruby project.

Start by creating a new directory for the project:

 
mkdir factorybot-demo && cd factorybot-demo

Initialize a Gemfile to manage dependencies:

 
bundle init

Open the Gemfile and add the required gems:

Gemfile
source 'https://rubygems.org'

gem 'activerecord', '~> 7.2'
gem 'sqlite3', '~> 2.3'
gem 'factory_bot', '~> 6.5'
gem 'rspec', '~> 3.13'
gem 'faker', '~> 3.5'

Install all dependencies:

 
bundle install

The faker gem is optional but highly recommended. It generates realistic fake data for your tests, making them more representative of production scenarios.

Create a basic database configuration file:

config/database.rb
require 'active_record'

ActiveRecord::Base.establish_connection(
  adapter: 'sqlite3',
  database: ':memory:'
)

Using an in-memory SQLite database ensures your tests run fast and don't leave artifacts on the filesystem.

Set up a simple data model. Create a models directory and add a User model:

 
mkdir models
models/user.rb
class User < ActiveRecord::Base
end

Create a database schema:

db/schema.rb
ActiveRecord::Schema.define do
  create_table :users do |t|
    t.string :email, null: false
    t.string :username, null: false
    t.string :first_name
    t.string :last_name
    t.boolean :admin, default: false
    t.timestamps
  end

  add_index :users, :email, unique: true
  add_index :users, :username, unique: true
end

This schema defines a users table with basic attributes and uniqueness constraints on email and username.

Now configure RSpec and FactoryBot. Create the RSpec configuration:

 
mkdir spec
spec/spec_helper.rb
require_relative '../config/database'
require_relative '../models/user'
require_relative '../db/schema'
require 'factory_bot'
require 'faker'

# Load the database schema only once
ActiveRecord::Schema.verbose = false
unless ActiveRecord::Base.connection.table_exists?(:users)
load File.join(__dir__, '..', 'db', 'schema.rb')
end
RSpec.configure do |config| config.include FactoryBot::Syntax::Methods config.before(:suite) do FactoryBot.find_definitions end config.around(:each) do |example| ActiveRecord::Base.transaction do example.run raise ActiveRecord::Rollback end end end

This configuration loads your database schema, includes FactoryBot's methods in your tests, and wraps each test in a database transaction that rolls back after completion. This ensures test isolation without manually cleaning the database.

Create a directory for your factory definitions:

 
mkdir spec/factories

Define your first factory:

spec/factories/users.rb
FactoryBot.define do
  factory :user do
    email { Faker::Internet.email }
    username { Faker::Internet.username(specifier: 5..12) }
    first_name { Faker::Name.first_name }
    last_name { Faker::Name.last_name }
    admin { false }
  end
end

This factory defines default values for all user attributes using Faker to generate realistic data.

Create your first test to verify the setup:

spec/user_spec.rb
require 'spec_helper'

RSpec.describe User do
  it 'creates a user with factory' do
    user = create(:user)

    expect(user).to be_persisted
    expect(user.email).to be_present
    expect(user.username).to be_present
  end
end

Run the test:

 
bundle exec rspec

You should see output confirming your test passed:

Output
-- create_table(:users)
   -> 0.0130s
-- add_index(:users, :email, {unique: true})
   -> 0.0011s
-- add_index(:users, :username, {unique: true})
   -> 0.0001s
.

Finished in 0.7108 seconds (files took 0.66575 seconds to load)
1 example, 0 failures

You've successfully created a user record using FactoryBot. The create method builds an object and saves it to the database, making it available for your test assertions.

Understanding FactoryBot strategies

FactoryBot provides different strategies for creating objects, each suited to specific testing needs. Understanding when to use each strategy helps you write faster and more focused tests.

The build strategy

The build strategy creates an object in memory without saving it to the database. This is useful when you need an object for testing logic that doesn't require persistence:

spec/user_spec.rb
require 'spec_helper'

RSpec.describe User do
  it 'creates a user with factory' do
    user = create(:user)

    expect(user).to be_persisted
    expect(user.email).to be_present
    expect(user.username).to be_present
  end

it 'builds a user without saving' do
user = build(:user)
expect(user).not_to be_persisted
expect(user.email).to be_present
expect(user.valid?).to be true
end
end

The built user exists in memory with all its attributes populated, but it hasn't been saved to the database. This allows you to test validations and other business logic without the overhead of database writes.

Run the tests:

 
bundle exec rspec
Output
..

Finished in 0.66897 seconds (files took 0.52166 seconds to load)
2 examples, 0 failures

The build strategy is significantly faster than create because it skips database operations. Use it whenever you don't need the object to be persisted.

The attributes_for strategy

The attributes_for strategy returns a hash of attributes without creating an object. This is particularly useful for testing controller actions or form submissions:

spec/user_spec.rb
require 'spec_helper'

RSpec.describe User do
  it 'creates a user with factory' do
    user = create(:user)

    expect(user).to be_persisted
    expect(user.email).to be_present
    expect(user.username).to be_present
  end

  it 'builds a user without saving' do
    ...
  end

it 'generates user attributes hash' do
attributes = attributes_for(:user)
expect(attributes).to be_a(Hash)
expect(attributes[:email]).to be_present
expect(attributes[:username]).to be_present
expect(attributes).not_to have_key(:id)
end
end

This returns a plain Ruby hash with all the factory attributes, excluding any database-generated fields. It's perfect for simulating form parameters in controller tests.

Run the tests again:

 
bundle exec rspec
Output
...

Finished in 0.67277 seconds (files took 0.92154 seconds to load)
3 examples, 0 failures

The attributes hash doesn't include database-generated fields like id or timestamps, making it perfect for simulating form submissions.

The build_stubbed strategy

The build_stubbed strategy creates an object that appears persisted but isn't actually saved to the database. It's the fastest strategy and ideal for unit tests that need objects with IDs but don't access the database:

spec/user_spec.rb
require 'spec_helper'

RSpec.describe User do
  it 'creates a user with factory' do
    ...
  end

  it 'builds a user without saving' do
    ...
  end

  it 'generates user attributes hash' do
    ..
  end

it 'stubs a user without database interaction' do
user = build_stubbed(:user)
expect(user.id).to be_present
expect(user.new_record?).to be false
expect(user.email).to be_present
end
end

The stubbed user has an ID and behaves like a persisted record, but it never touches the database. This makes it the fastest option for pure unit tests.

Run all the tests:

 
bundle exec rspec
Output
....

Finished in 0.06123 seconds (files took 0.23451 seconds to load)
4 examples, 0 failures

Objects created with build_stubbed have IDs and appear persisted, but any attempt to save or reload them will raise an error. This makes your tests fail fast if they accidentally try to hit the database.

Customizing factory attributes

While factories provide default values, you often need to override specific attributes for individual tests. FactoryBot makes this straightforward.

Overriding attributes

You can override any attribute by passing it as an argument to the strategy method:

spec/user_spec.rb
require 'spec_helper'

RSpec.describe User do
  it 'creates a user with factory' do
    ...
  end

  it 'builds a user without saving' do
    ...
  end

  it 'generates user attributes hash' do
    ..
  end

  it 'stubs a user without database interaction' do
    user = build_stubbed(:user)

    expect(user.id).to be_present
    expect(user.new_record?).to be false
    expect(user.email).to be_present
  end

it 'overrides factory attributes' do
user = create(:user, email: 'custom@example.com', admin: true)
expect(user.email).to eq('custom@example.com')
expect(user.admin).to be true
end
end

This test demonstrates how to customize specific attributes while keeping the factory defaults for everything else. The email and admin status are overridden, but firstname, lastname, and username still use the values from the factory definition.

Run the tests:

 
bundle exec rspec
Output
.....

Finished in 0.65964 seconds (files took 0.51818 seconds to load)
5 examples, 0 failures

Any attributes you specify override the factory defaults, while unspecified attributes still use the factory definitions.

Using sequences

Sequences generate unique values for attributes that must be unique, like email addresses or usernames. Update your factory to use sequences:

spec/factories/users.rb
FactoryBot.define do
  factory :user do
sequence(:email) { |n| "user#{n}@example.com" }
sequence(:username) { |n| "user#{n}" }
first_name { Faker::Name.first_name } last_name { Faker::Name.last_name } admin { false } end end

Sequences use a counter (represented by n) that increments with each object creation, ensuring every generated email and username is unique.

Add a test that creates multiple users:

spec/user_spec.rb
require 'spec_helper'

RSpec.describe User do
  it 'creates a user with factory' do
    ...
  end

  it 'builds a user without saving' do
    ...
  end

  it 'generates user attributes hash' do
    ...
  end

  it 'stubs a user without database interaction' do
    ...
  end

  it 'overrides factory attributes' do
    user = create(:user, email: 'custom@example.com', admin: true)

    expect(user.email).to eq('custom@example.com')
    expect(user.admin).to be true
  end

it 'generates unique values with sequences' do
user1 = create(:user)
user2 = create(:user)
user3 = create(:user)
# Verify all emails are unique
emails = [user1.email, user2.email, user3.email]
expect(emails.uniq.length).to eq(3)
# Verify all usernames are unique
usernames = [user1.username, user2.username, user3.username]
expect(usernames.uniq.length).to eq(3)
# Verify they follow the sequence pattern
expect(user1.email).to match(/user\d+@example\.com/)
expect(user1.username).to match(/user\d+/)
end
end

This test verifies that each user gets a unique email and username based on the sequence counter.

Run the tests:

 
bundle exec rspec
Output
......

Finished in 0.6668 seconds (files took 0.50861 seconds to load)
6 examples, 0 failures

Sequences automatically increment each time you create an object, ensuring unique values without manual management. This is especially important for attributes with database uniqueness constraints.

Final Thoughts

As your Ruby projects grow in complexity, managing test data effectively becomes critical. FactoryBot not only replaces traditional fixtures but also gives you the flexibility to build, customize, and scale your test setup with ease. From simple object creation to handling unique constraints and complex associations, it helps keep your tests both fast and maintainable.

To continue exploring its full capabilities and best practices, check out the official documentation here: FactoryBot GitHub Repository.

Got an article suggestion? Let us know
Next article
Getting Started with Capybara
Learn how to use Capybara for integration testing in Ruby applications. This guide covers setup, element interaction, JavaScript testing, and async handling.
Licensed under CC-BY-NC-SA

This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.