# FactoryBot for Test Data Management in Ruby

[FactoryBot](https://github.com/thoughtbot/factory_bot) 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.

[ad-logs]

## 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](https://rspec.info/) or [Minitest](https://github.com/minitest/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:

```command
mkdir factorybot-demo && cd factorybot-demo
```

Initialize a Gemfile to manage dependencies:

```command
bundle init
```

Open the `Gemfile` and add the required gems:

```ruby
[label Gemfile]
source 'https://rubygems.org'

[highlight]
gem 'activerecord', '~> 7.2'
gem 'sqlite3', '~> 2.3'
gem 'factory_bot', '~> 6.5'
gem 'rspec', '~> 3.13'
gem 'faker', '~> 3.5'
[/highlight]
```

Install all dependencies:

```command
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:

```ruby
[label 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:

```command
mkdir models
```

```ruby
[label models/user.rb]
class User < ActiveRecord::Base
end
```

Create a database schema:

```ruby
[label 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:

```command
mkdir spec
```

```ruby
[label 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
[highlight]
unless ActiveRecord::Base.connection.table_exists?(:users)
  load File.join(__dir__, '..', 'db', 'schema.rb')
end
[/highlight]

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:

```command
mkdir spec/factories
```

Define your first factory:

```ruby
[label 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:

```ruby
[label 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:

```command
bundle exec rspec
```

You should see output confirming your test passed:

```text
[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:

```ruby
[label 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

  [highlight]
  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
  [/highlight]
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:

```command
bundle exec rspec
```

```text
[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:

```ruby
[label 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

  [highlight]
  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
  [/highlight]
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:

```command
bundle exec rspec
```

```text
[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:

```ruby
[label 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

  [highlight]
  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
  [/highlight]
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:

```command
bundle exec rspec
```

```text
[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:

```ruby
[label 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

  [highlight]
  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
  [/highlight]
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 first_name, last_name, and username still use the values from the factory definition.

Run the tests:

```command
bundle exec rspec
```

```text
[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:

```ruby
[label spec/factories/users.rb]
FactoryBot.define do
  factory :user do
    [highlight]
    sequence(:email) { |n| "user#{n}@example.com" }
    sequence(:username) { |n| "user#{n}" }
    [/highlight]
    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:

```ruby
[label 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

  [highlight]
  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
  [/highlight]
end
```

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

Run the tests:

```command
bundle exec rspec
```

```text
[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](https://github.com/thoughtbot/factory_bot).