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.
Provides guidance on creating and using Factory Bot for test data in RSpec.
/plugin marketplace add bastos/claude-code-plugin-marketplace/plugin install bastos-rspec-plugins-rspec@bastos/claude-code-plugin-marketplaceThis skill inherits all available tools. When active, it can use any tool Claude has access to.
Factory Bot provides a framework for setting up Ruby objects as test data. It replaces fixtures with a more flexible and maintainable approach.
# Gemfile
group :development, :test do
gem "factory_bot_rails"
end
# spec/rails_helper.rb
RSpec.configure do |config|
config.include FactoryBot::Syntax::Methods
end
# spec/factories/users.rb
FactoryBot.define do
factory :user do
first_name { "John" }
last_name { "Doe" }
email { "john@example.com" }
password { "password123" }
admin { false }
end
end
# Create and save to database
user = create(:user)
# Build without saving (in-memory)
user = build(:user)
# Create with attribute overrides
user = create(:user, first_name: "Jane", admin: true)
# Build stubbed (fastest, no database)
user = build_stubbed(:user)
# Get attributes as hash
attrs = attributes_for(:user)
Generate unique values:
factory :user do
sequence(:email) { |n| "user#{n}@example.com" }
sequence(:username) { |n| "user_#{n}" }
end
# Creates: user1@example.com, user2@example.com, etc.
# Named sequences (reusable)
sequence :email do |n|
"person#{n}@example.com"
end
factory :user do
email { generate(:email) }
end
factory :admin do
email { generate(:email) }
end
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
# Faker for realistic data
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
factory :post do
title { "My Post" }
body { "Content" }
user # Automatically creates associated user
end
# Override association
post = create(:post, user: specific_user)
# Explicit association
factory :post do
association :user
# or with factory name different from attribute
association :author, factory: :user
end
factory :user do
first_name { "John" }
# Using transient + callback
transient do
posts_count { 0 }
end
after(:create) do |user, evaluator|
create_list(:post, evaluator.posts_count, user: user)
end
end
# Usage
user = create(:user, posts_count: 3)
user.posts.count # => 3
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)
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
# Usage
admin = create(:user, :admin)
inactive_admin = create(:user, :admin, :inactive)
user_with_avatar = create(:user, :with_avatar)
# Multiple traits
create(:user, :admin, :with_avatar, :inactive)
# Traits with overrides
create(:user, :admin, first_name: "Super Admin")
# Factory inheriting traits
factory :admin, traits: [:admin]
factory :inactive_user, traits: [:inactive]
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)
Execute code at different lifecycle points:
factory :user do
after(:build) do |user|
# After building, before save
end
before(:create) do |user|
# Just before save
end
after(:create) do |user|
# After save
end
after(:stub) do |user|
# After build_stubbed
end
end
# Callback with evaluator (access transient attributes)
after(:create) do |user, evaluator|
create_list(:post, evaluator.posts_count, user: user)
end
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) # Regular user
create(:admin) # Admin user
create(:super_admin) # Super admin
| 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 |
# Performance comparison
build(:user) # Fast, no database
build_stubbed(:user) # Fastest, fake persistence
create(:user) # Slowest, hits database
# build - testing object behavior without persistence
user = build(:user)
expect(user).to be_valid
# build_stubbed - mocking persisted objects
user = build_stubbed(:user)
expect(user.id).to be_present # Has fake ID
# create - testing database interactions
user = create(:user)
expect(User.find(user.id)).to eq(user)
# attributes_for - controller params
post users_path, params: { user: attributes_for(:user) }
# Create multiple records
users = create_list(:user, 3)
users = create_list(:user, 3, admin: true)
# Build multiple (no save)
users = build_list(:user, 5)
# With different attributes
users = create_list(:user, 3) do |user, i|
user.update!(position: i)
end
Validate all factories are correctly defined:
# spec/factories_spec.rb
RSpec.describe "Factories" do
it "has valid factories" do
FactoryBot.lint
end
# Faster: only lint traits
it "has valid factories" do
FactoryBot.lint(traits: true)
end
end
Define only required attributes:
# Good - minimal
factory :user do
email { generate(:email) }
password { "password" }
end
# Avoid - too much default data
factory :user do
email { generate(:email) }
password { "password" }
first_name { "John" }
last_name { "Doe" }
phone { "555-1234" }
address { "123 Main St" }
# ... lots more
end
# Good - traits for specific states
factory :order do
trait :pending do
status { "pending" }
end
trait :shipped do
status { "shipped" }
shipped_at { 1.day.ago }
end
end
# Avoid - many separate factories
factory :pending_order
factory :shipped_order
factory :cancelled_order
# Good - build associations only when needed
let(:user) { create(:user) }
let(:post) { create(:post, user: user) }
# Avoid - creating unused associated records
let(:user) { create(:user, :with_posts, :with_comments) }
references/factory-patterns.md - Advanced factory patternsreferences/faker-examples.md - Faker gem examplesexamples/factories/users.rb - Complete user factoryexamples/factories/orders.rb - Complex association example