Factory Bot — Test Data Factories in Rails
In this tutorial, you will learn about Factory Bot. We cover key concepts, practical examples, and best practices to help you master this topic.
Factory Bot creates test data with factories, traits, sequences, associations, and callbacks, supporting build, create, and attributes_for strategies for different testing needs.
What You'll Learn
By the end of this tutorial, you'll define factories with traits and sequences, handle associations, use transient attributes, implement callbacks, and choose the right creation Strategy.
Why Factory Bot Matters
Factory Bot replaces fixtures with flexible, maintainable test data definitions. Traits modify data for specific scenarios without duplicating factory definitions.
Real-World Use
A blog test suite uses a Post factory with traits for published, drafted, and featured states. A User factory generates unique emails via sequences and creates associated profiles.
Factory Path
flowchart LR
A[RSpec Testing] --> B[Factory Bot]
B --> C[Factories]
B --> D[Traits]
B --> E[Associations]
B --> F[Sequences]
B --> G{You Are Here}
style G fill:#f90,color:#fff
Defining Factories
Create test data factories.
# spec/factories/users.rb
FactoryBot.define do
factory :user do
name { "John Doe" }
email { generate(:email) }
password { "password123" }
password_confirmation { "password123" }
confirmed_at { Time.current }
trait :admin do
role { :admin }
end
trait :unconfirmed do
confirmed_at { nil }
end
end
end
Sequences
Generate unique values.
FactoryBot.define do
sequence(:email) { |n| "user#{n}@example.com" }
sequence(:sku) { |n| "SKU-#{n.to_s.rjust(5, "0")}" }
sequence(:slug) { |n| "post-slug-#{n}" }
factory :product do
name { "Product #{generate(:sku)}" }
sku
price { 9.99 }
association :category
trait :expensive do
price { 999.99 }
end
end
end
Associations
Define model associations in factories.
FactoryBot.define do
factory :post do
title { "My Post" }
body { "Post body content" }
association :author, factory: :user
trait :with_comments do
after(:create) do |post|
create_list(:comment, 3, post: post)
end
end
trait :with_tags do
after(:create) do |post|
post.tags << create_list(:tag, 2)
end
end
end
factory :comment do
body { "Great post!" }
association :post
association :author, factory: :user
end
end
Transient Attributes
Use transient attributes for conditional setup.
FactoryBot.define do
factory :order do
transient do
item_count { 1 }
total { nil }
end
status { :pending }
association :user
after(:create) do |order, evaluator|
create_list(:order_item, evaluator.item_count, order: order)
if evaluator.total
order.update_column(:total, evaluator.total)
else
order.update_column(:total, order.items.sum(&:total))
end
end
trait :with_items do
item_count { 3 }
end
end
end
# Usage
create(:order) # 1 item
create(:order, item_count: 5) # 5 items
create(:order, total: 100.0) # Fixed total
Callbacks
Run code during factory lifecycle.
FactoryBot.define do
factory :invoice do
association :order
after(:build) do |invoice|
invoice.invoice_number ||= "INV-#{Time.current.to_i}"
end
after(:create) do |invoice|
InvoiceMailer.send_invoice(invoice).deliver_later
end
after(:stub) do |invoice|
invoice.id ||= 123
end
end
end
Common Mistakes
1. Slow Factory Creation
Creating deeply nested associations in factories makes tests slow. Use build_stubbed for unit tests.
2. Overusing Traits
Too many traits make factories hard to read. Combine traits with transient attributes.
3. Not Using build_stubbed
build_stubbed creates in-memory objects without database persistence. Use it for non-query tests.
4. Circular Factory Dependencies
Factory A creates Factory B which creates Factory A. Use association without factory creation.
5. Hardcoded Values That Cause Uniqueness Violations
Always use sequences for unique fields like email or slug.
Practice Questions
1. What is the difference between build and create?
build creates an in-memory object. create persists to the database.
2. What is a trait in Factory Bot?
A named variation of a factory that overrides or adds attributes.
3. How do you create a list of records?
Use create_list(:product, 5) to create 5 products.
4. What is build_stubbed for?
Creates a fake record that acts like a persisted record but is not saved.
5. Challenge: Create a factory with associations and traits for a blog.
FactoryBot.define do
factory :article do
title { "Article Title" }
body { "Article body" }
association :author, factory: :user
trait :published do
published_at { 1.hour.ago }
end
trait :draft do
published_at { nil }
end
trait :with_comments do
after(:create) { |a| create_list(:comment, 3, article: a) }
end
end
end
FAQ
Mini Project: Complete Factory Suite
Create factories for an e-commerce application.
FactoryBot.define do
factory :product do
name { "Widget" }
price { 19.99 }
association :category
trait :on_sale do
sale_price { 14.99 }
end
trait :out_of_stock do
quantity { 0 }
end
end
factory :order do
association :user
transient do
products_count { 1 }
end
after(:create) do |order, eval|
create_list(:order_item, eval.products_count, order: order)
end
end
end
What's Next
Rails Faker Rails Shoulda Matchers Rails System Tests
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro