FactoryBot Factories in Rails Tests
Fast recall
Quick revision
Core answer
build(:user) creates an unsaved object, create(:user) persists it, attributes_for(:user) returns attributes, and traits name meaningful variations such as :admin. Factories are useful when tests need valid records with small overrides, but they should not hide large object graphs or replace explicit scenario setup. Use the cheapest build strategy that satisfies the behaviour under test.Whether you know factories (FactoryBot) build test objects with default valid data, vs fixtures, and create/build/traits.
Over-stuffing factories with data the test doesn't need (slow, brittle) — keep them lean; or sharing fixtures across tests (hidden coupling).
Factories (FactoryBot): build test objects with valid defaults. create (saved), build (unsaved), traits for variations. Lean factories; fixtures are faster.
Interview-ready guide
Interview answer
A FactoryBot factory is a named recipe for constructing a valid test object with sensible defaults. A test overrides only the values that create its scenario. The factory removes repetitive setup, but it does not prove behaviour; the test still needs an action and an assertion.
Use build(:user) for an unsaved model, create(:user) when persistence or relationships matter, and attributes_for(:user) when a request needs an attribute hash. build_stubbed(:user) is useful for simple persisted-like reads, but it cannot reproduce real queries or persistence callbacks.
Traits describe meaningful variations without copying the base factory. build(:user, :suspended) tells the reader why this user matters, while the factory supplies ordinary validity details. Keep the decisive state visible in the test and prefer domain words such as suspended, admin, or expired.
Factories become harmful when create(:order) silently builds customers, products, invoices, and callbacks the example never uses. Keep base recipes minimal, use explicit helpers for precise object graphs, and choose build unless persistence is part of the contract. Setup should explain the scenario rather than hide it.
FactoryBot Factories in Rails Tests
Direct Answer
A FactoryBot factory is a reusable recipe for constructing test objects with sensible defaults. build(:user) creates an unsaved object, create(:user) persists it, attributes_for(:user) returns attributes, and traits name meaningful variations such as :admin. Factories are useful when tests need valid records with small overrides, but they should not hide large object graphs or replace explicit scenario setup. Use the cheapest build strategy that satisfies the behaviour under test.
Deep Dive & Architecture
A useful factory owns boring validity defaults: a unique email format, a required name, or a normal state. The test owns the values that explain why the behaviour changes. If a discount test depends on account_tier: :gold, that override should be visible near the action and expectation.
Build strategies change both speed and semantics. build instantiates without saving, create persists through the model lifecycle, attributes_for supplies a plain hash, and build_stubbed presents a persisted-like object without a database write. The last option cannot reproduce every Active Record interaction, so it should not be used when queries or persistence callbacks are part of the contract.
Traits are named states that can be composed. They work well for :admin, :expired, or :with_items when those words match the domain. Too many callback-heavy traits can generate surprising combinations; explicit helper code is clearer when several records must form a precise relationship.
Factories and fixtures are alternatives for preparing data, not competitors in assertion quality. Fixtures provide stable named rows and are built into Rails. Factories construct data through recipes and local overrides. A team can choose one or combine them deliberately, provided the scenario remains clear and the setup cost stays controlled.
Code Example
FactoryBot.define do
factory :user do
sequence(:email) { |number| "user#{number}@example.com" }
status { :active }
trait :suspended do
status { :suspended }
suspended_at { Time.current }
end
end
end
user = build(:user, :suspended)
expect(user).to be_suspended
# Use create only when the example needs a stored record.
Common Mistakes & Pitfalls
Over-stuffing factories with data the test doesn't need (slow, brittle) — keep them lean; or sharing fixtures across tests (hidden coupling).
Check your understanding
Answer these without looking back at the article.
- What is the difference between `build`, `create`, and `attributes_for`?
- How can factories make a Rails test suite slow or confusing?
- When might a fixture be clearer than a factory?
Looking for a structured roadmap?
Every module on this page is also part of the full Java backend interview track.