Overview
A catalog of six ways to construct test data. Pick the pattern here, then configure the tool with faker-data or synthetic-data-toolkit.
How to use this catalog
Do not use this skill to:
Pattern 1 - Test Data Builder
Canonical source: Nat Pryce and Steve Freeman, the Test Data Builder pattern (see References).
Definition: A Test Data Builder is a class with chainable methods (.withName("Alice").withOrgId(42).build()) that constructs domain objects step-by-step. Every field has a sensible default; the test overrides only the fields it cares about.
Concrete implementation (TypeScript, runnable):
type User = { role: string; org: { plan: string } };
class UserBuilder {
private user: User = { role: "standard", org: { plan: "free" } };
withRole(role: string) { this.user.role = role; return this; }
withOrg(org: User["org"]) { this.user.org = org; return this; }
build(): User { return { ...this.user }; }
}
const aUser = () => new UserBuilder();
// Test overrides only the fields it cares about:
const admin = aUser().withRole("admin").build();When to use Test Data Builder
Anti-patterns
| Anti-pattern | Why it fails |
|---|---|
Builders with .set<Field>(value) for every field (no defaults) | Loses the pattern's benefit; every test specifies every field |
| Builders that mutate the object in place rather than returning a new one | Test cross-coupling: builders shared between tests leak state |
Builders that perform side effects (build() writes to DB) | Mixes two concerns; the test cannot tell whether it's constructing or persisting |
| Builders for objects with 2 fields | Overhead exceeds benefit; use a struct literal |
Pattern 2 - Factory (with traits and associations)
Canonical source: Thoughtbot's factory_bot (Ruby) is the cross-language reference implementation; the pattern adapts the Gang of Four Factory Method for test data.
Definition: A Factory is a registered, named template for creating an object. Traits are named modifiers (:admin, :disabled, :premium) that compose with the base template. Associations express relationships (user.org, org.plan).
Example (Ruby FactoryBot, the reference implementation):
factory :user do
name { Faker::Name.name }
email { Faker::Internet.email }
trait :admin do
role { :admin }
end
trait :with_org do
association :org, factory: :org
end
end
# Test usage:
admin_user = create(:user, :admin, :with_org)The same factory in Python (factory_boy reference (opens in new window)): traits live in class Params and switch on with one boolean; SubFactory builds the association with the caller's strategy, so build() saves nothing.
import factory
class UserFactory(factory.Factory):
class Meta:
model = User
name = factory.Faker("name")
email = factory.Faker("email")
role = "standard"
org = None
class Params:
admin = factory.Trait(role="admin")
with_org = factory.Trait(org=factory.SubFactory(OrgFactory))
admin_user = UserFactory.build(admin=True, with_org=True) # not persistedCross-language equivalents (canonical per-language tool; URLs in References):
When to use Factory
Anti-patterns
| Anti-pattern | Why it fails |
|---|---|
Factory definitions that hard-code IDs (id: 1) | Tests collide in parallel; factories must let the DB / Faker assign |
Traits that overlap silently (:admin and :premium both set role) | Order-dependent behaviour; trait composition becomes unpredictable |
Factories that persist by default (create is the only mode) | Slow tests, unnecessary DB writes. Use the build strategy unless the test reads from the DB (synthetic-data-toolkit, FactoryBot reference) |
| One mega-factory for the entire domain | Becomes a god-object; every test pulls a fully-populated graph |
Pattern 3 - Object Mother
Canonical source: Martin Fowler, Object Mother (see References). Predates Test Data Builder; superseded by it for most use cases but still useful for stable canonical objects.
Definition: A central class (the "Mother") exposes named methods that return fully-constructed canonical test objects: ObjectMother.standardUser(), ObjectMother.adminUser(), ObjectMother.userInOrgWithFiveMembers().
Fowler's framing: "An Object Mother is a class that contains methods that create well-known objects for use in tests." Useful when the team has a small, stable set of canonical fixtures.
When to use Object Mother
Anti-patterns
| Anti-pattern | Why it fails |
|---|---|
| Object Mother that grows to 50+ methods | Becomes a god-class; engineers can't find the right factory |
| Methods that return shared mutable instances | Tests cross-couple through the Mother's return values |
| Mixing Mother with Builder (some objects via Mother, some via Builder) | Inconsistent test idiom; vocabulary drift |
Mother methods with implicit dependencies (adminUser() requires seedOrgs() to have run) | Implicit ordering creates flakiness |
Pattern 4 - Fixture composition
Canonical source: Gerard Meszaros, xUnit Test Patterns - the seminal reference for Fresh Fixture, Shared Fixture, Implicit Setup, Delegated Setup, and Setup Decorator patterns; it defines the four-phase test pattern (setup / exercise / verify / teardown). See References.
Definition: Fixture composition is the pattern of building per-test state from reusable fragments. The three flavours:
| Flavour | When |
|---|---|
| Fresh Fixture | Each test creates its own state from scratch. Most isolated; slowest. |
| Shared Fixture | Multiple tests share one initialised state. Fastest; brittle to test ordering. |
| Persistent Fresh Fixture | Fresh state per test, but persisted in a transaction that rolls back at teardown. The pragmatic middle ground. |
Fowler prefers rebuilding state per test because failures are easier to trace, and switches to clean-up only when rebuilding is too slow (Eradicating Non-Determinism in Tests (opens in new window)).
When to use Fresh Fixture (default)
When to use Shared Fixture
Anti-patterns
| Anti-pattern | Why it fails |
|---|---|
| Shared Fixture that some tests quietly mutate | Cross-test coupling; failures depend on test order |
| Fresh Fixture for a 30-minute E2E seed | Test suite time becomes infeasible; team starts skipping tests |
| Multiple fixture flavours in the same suite without explicit convention | Engineers can't tell what to write; bugs creep in |
| Fixture inheritance hierarchies >2 levels deep | Depth-3+ chains break unpredictably |
Pattern 5 - Snapshot / golden-file
Canonical source: Jest snapshot testing is the cross-language reference for the test-code side; Michael Feathers' Working Effectively with Legacy Code coined "characterisation tests", the legacy-code-tier version of snapshot testing. See References.
Definition: A snapshot test compares the current output of code under test to a previously-saved canonical output ("the golden file"). When the test runs, it serialises the output, compares against the file, fails if they differ. Engineers explicitly approve a new golden file when the change is intentional.
This skill's role: Snapshot is a recurring concept in test-data conversation, but the operational details (file naming, sanitisation of timestamps / IDs / PII, per-OS variants, review workflow) are documented in detail by golden-file-conventions. Reach for that skill for the operational catalog; this section is the pattern's catalog entry only.
When to use Snapshot
When NOT to use Snapshot
Pattern 6 - Production-Data Anonymisation
Canonical source: GDPR Recital 26: pseudonymised data is still personal data, and only truly anonymous information falls outside the regulation (Recital 26 (opens in new window)). synthetic-pii-generator is the companion PII-synthesis skill.
Definition: Using production data (or production-shaped data) for testing only after removing or masking personal data, commercially sensitive data, and any field that would breach privacy or compliance if leaked from a test environment.
The three anonymisation flavours
| Flavour | Definition | When |
|---|---|---|
| Masking | Replace sensitive fields with deterministic placeholders (X*** for surnames; static fake date) | Production-shape preserved; field-level reversible if needed |
| Synthesis | Generate fake data that statistically resembles production (length distributions, locale mix) | No mapping back to production; safest |
| Tokenisation | Replace sensitive values with tokens that map back via a secured lookup | Round-tripping to production (rare in QA). This is pseudonymisation: the data is still personal data under GDPR |
Anti-patterns
| Anti-pattern | Why it fails |
|---|---|
| Copying production DB to staging "for realism", even "internal only" | Test environments have weaker access control; raw personal data leaks from there |
| Treating masked or tokenised data as anonymous | Reversible or linkable data is still personal data (Recital 26) |
| Anonymisation that preserves the join keys | Sensitive relations (who bought what) survive the anonymisation |
| Anonymisation in CI that doesn't anonymise in dev local | Engineers have raw prod data on their laptops |
| Anonymisation as a one-time operation | Production data changes; anonymisation must run continuously |
Pattern-selection guide
| Need | Pattern | When to mix |
|---|---|---|
| Few-fields, default-most strategy | Test Data Builder | Use Factory underneath the Builder for DB persistence |
| Many variants of one entity | Factory with traits | Combine with Builder for the test-API surface |
| Small stable set of canonical objects | Object Mother | Generally legacy; consider migrating to Builder + Factory |
| Per-test independence | Fresh Fixture | Always the default; reach for Shared only when measured slow |
| Read-only shared state | Shared Fixture | Document immutability; one mutation kills the contract |
| Large structured output | Snapshot / golden-file | See golden-file-conventions for operational details |
| Production-shaped privacy-safe data | Anonymisation | Always for production-sourced data; pair with synthetic-pii-generator |
Cross-cutting anti-patterns
| Anti-pattern | Why it fails |
|---|---|
| Mixing all six patterns in one codebase | Engineers can't tell what to write; vocabulary fragments |
Test data inline-literaled in tests ({ name: "Alice", id: 1 }) at scale | Refactors break 200 tests when one schema field changes |
| Test data setup that takes >5s per test | Suite time becomes infeasible; teams skip tests |
Data construction and persistence collapsed into one method (createUser() always writes to DB) | Cannot test the construction logic without the DB |
| Implicit Setup (relies on global state from a previous test) | Tests become order-dependent; flake follows |
| Test data with PII / production keys | Compliance + security breach surface |