| name | factory-bot |
| description | This skill should be used when the user asks about "factories", "FactoryBot", "factory_bot", "traits", "sequences", "build vs create", "transient attributes", "associations in factories", or needs guidance on test data generation in RSpec. |
| version | 1.0.0 |
Factory Bot
Factory Bot provides a framework for setting up Ruby objects as test data. It replaces fixtures with a more flexible and maintainable approach.
Setup
group :development, :test do
gem "factory_bot_rails"
end
RSpec.configure do |config|
config.include FactoryBot::Syntax::Methods
end
Basic Factories
Defining Factories
FactoryBot.define do
factory :user do
first_name { "John" }
last_name { "Doe" }
email { "john@example.com" }
password { "password123" }
admin { false }
end
end
Using Factories
user = create(:user)
user = build(:user)
user = create(:user, first_name: "Jane", admin: true)
user = build_stubbed(:user)
attrs = attributes_for(:user)
Sequences
Generate unique values:
factory :user do
sequence(:email) { |n| "user#{n}@example.com" }
sequence(:username) { |n| "user_#{n}" }
end
sequence :email do |n|
"person#{n}@example.com"
end
factory :user do
email { generate(:email) }
end
factory :admin do
email { generate(:email) }
end
Dynamic Attributes
Use blocks for computed values:
factory :user do
first_name { "John" }
last_name { "Doe" }
email { "#{first_name.downcase}.#{last_name.downcase}@example.com" }
created_at { Time.current }
birth_date { 25.years.ago }
end
factory :user do
first_name { Faker::Name.first_name }
last_name { Faker::Name.last_name }
email { Faker::Internet.email }
phone { Faker::PhoneNumber.phone_number }
bio { Faker::Lorem.paragraph }
end
Associations
belongs_to
factory :post do
title { "My Post" }
body { "Content" }
user
end
post = create(:post, user: specific_user)
factory :post do
association :user
association :author, factory: :user
end
has_many
factory :user do
first_name { "John" }
transient do
posts_count { 0 }
end
after(:create) do |user, evaluator|
create_list(:post, evaluator.posts_count, user: user)
end
end
user = create(:user, posts_count: 3)
user.posts.count
has_many with trait
factory :user do
trait :with_posts do
transient do
posts_count { 3 }
end
after(:create) do |user, evaluator|
create_list(:post, evaluator.posts_count, user: user)
end
end
end
user = create(:user, :with_posts)
user = create(:user, :with_posts, posts_count: 5)
Traits
Define reusable attribute sets:
factory :user do
first_name { "John" }
email { "john@example.com" }
admin { false }
trait :admin do
admin { true }
email { "admin@example.com" }
end
trait :with_avatar do
after(:create) do |user|
user.avatar.attach(
io: File.open(Rails.root.join("spec/fixtures/avatar.png")),
filename: "avatar.png"
)
end
end
trait :inactive do
active { false }
deactivated_at { 1.day.ago }
end
end
admin = create(:user, :admin)
inactive_admin = create(:user, :admin, :inactive)
user_with_avatar = create(:user, :with_avatar)
Combining Traits
create(:user, :admin, :with_avatar, :inactive)
create(:user, :admin, first_name: "Super Admin")
factory :admin, traits: [:admin]
factory :inactive_user, traits: [:inactive]
Transient Attributes
Pass data to factory without setting attributes:
factory :user do
transient do
upcased { false }
skip_confirmation { false }
end
first_name { "John" }
after(:create) do |user, evaluator|
user.update!(first_name: user.first_name.upcase) if evaluator.upcased
user.confirm! if evaluator.skip_confirmation
end
end
create(:user, upcased: true)
create(:user, skip_confirmation: true)
Callbacks
Execute code at different lifecycle points:
factory :user do
after(:build) do |user|
end
before(:create) do |user|
end
after(:create) do |user|
end
after(:stub) do |user|
end
end
after(:create) do |user, evaluator|
create_list(:post, evaluator.posts_count, user: user)
end
Inheritance
factory :user do
first_name { "John" }
email { "john@example.com" }
factory :admin do
admin { true }
email { "admin@example.com" }
end
factory :super_admin, parent: :admin do
super_admin { true }
end
end
create(:user)
create(:admin)
create(:super_admin)
Build Strategies
| Strategy | Database | ID | Best For |
|---|
create | Yes | Real | Integration tests |
build | No | nil | Unit tests, validations |
build_stubbed | No | Fake | Fastest, isolated tests |
attributes_for | No | - | Controller params |
build(:user)
build_stubbed(:user)
create(:user)
When to Use Each
user = build(:user)
expect(user).to be_valid
user = build_stubbed(:user)
expect(user.id).to be_present
user = create(:user)
expect(User.find(user.id)).to eq(user)
post users_path, params: { user: attributes_for(:user) }
Factory Lists
users = create_list(:user, 3)
users = create_list(:user, 3, admin: true)
users = build_list(:user, 5)
users = create_list(:user, 3) do |user, i|
user.update!(position: i)
end
Linting Factories
Validate all factories are correctly defined:
RSpec.describe "Factories" do
it "has valid factories" do
FactoryBot.lint
end
it "has valid factories" do
FactoryBot.lint(traits: true)
end
end
Best Practices
Minimal Factories
Define only required attributes:
factory :user do
email { generate(:email) }
password { "password" }
end
factory :user do
email { generate(:email) }
password { "password" }
first_name { "John" }
last_name { "Doe" }
phone { "555-1234" }
address { "123 Main St" }
end
Use Traits for Variations
factory :order do
trait :pending do
status { "pending" }
end
trait :shipped do
status { "shipped" }
shipped_at { 1.day.ago }
end
end
factory :pending_order
factory :shipped_order
factory :cancelled_order
Avoid Building What You Don't Need
let(:user) { create(:user) }
let(:post) { create(:post, user: user) }
let(:user) { create(:user, :with_posts, :with_comments) }