CtrlK
BlogDocsLog inGet started
Tessl Logo
PluginPublic

testland/test-data-patterns

Use when choosing how a test suite builds its test data, or when fixtures have drifted into shared mutable state. Explains Test Data Builder, Factory, Object Mother, fixture composition, snapshots and production-data anonymization, and when each fits, before picking a tool.

Quality

85%

Does it follow best practices?

Run evals on this skill

Adds up to 20 points to the overall score

View guide
SecuritybySnyk

Passed

No findings from the security scan

Overview
Quality
Evals
Security
Files
name:
test-data-patterns
description:
Use when choosing how a test suite builds its test data, or when fixtures have drifted into shared mutable state. Explains Test Data Builder, Factory, Object Mother, fixture composition, snapshots and production-data anonymization, and when each fits, before picking a tool.

test-data-patterns

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

  1. Inventory. List how the suite builds data today: grep for inline object literals, shared setUp / beforeAll state, factory files, snapshot files, and any production dump loaded into test databases.
  2. Pick. For each entity, choose a pattern from the Pattern-selection guide. One construction pattern per entity type; Builder over Factory is the only common mix.
  3. Check. Run the pattern's anti-pattern table against the existing code. Stop and fix any production data that is not anonymised before anything else (Pattern 6).
  4. Hand off. Configure the tool with the skill in Hand-off targets, then re-run the suite twice in random order (pytest --random-order with pytest-random-order, jest --randomize) to confirm no test depends on another's data.

Do not use this skill to:

  • Configure a specific tool - that's the per-language skill for that tool.
  • Generate negative / boundary / parameterized test data - that's negative-test-generator, boundary-value-generator, pairwise-test-case-generator.
  • Author an E2E seed fixture for the whole suite - that's seed-data-curator.

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

  • Domain objects have 5+ fields and most tests only care about 1-2.
  • The team values explicit "what this test cares about" in the test body.
  • The language has chainable / fluent API support (most modern languages).

Anti-patterns

Anti-patternWhy 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 oneTest 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 fieldsOverhead 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): 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 persisted

Cross-language equivalents (canonical per-language tool; URLs in References):

  • Ruby: factory_bot - the origin.
  • Python: factory_boy - direct port.
  • JS/TS: fishery, @faker-js/faker (lower-level).
  • .NET: Bogus - Faker-style with factory affordances.
  • Java: data-faker + handwritten factory or test-data-builder-style libraries.

When to use Factory

  • The project uses a database (factories handle FK relationships via associations).
  • The team is on Ruby / Python / JS-TS where mature factory libraries exist.
  • Domain objects have many variants (admin / disabled / premium / legacy) - traits express each.

Anti-patterns

Anti-patternWhy 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 domainBecomes 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

  • The domain has a small, stable set of canonical objects ("the test admin", "the seed catalogue").
  • The team is on a language without first-class factory libraries (older Java, C++, legacy stacks).
  • The Test Data Builder pattern is overkill for the small number of cases.

Anti-patterns

Anti-patternWhy it fails
Object Mother that grows to 50+ methodsBecomes a god-class; engineers can't find the right factory
Methods that return shared mutable instancesTests 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:

FlavourWhen
Fresh FixtureEach test creates its own state from scratch. Most isolated; slowest.
Shared FixtureMultiple tests share one initialised state. Fastest; brittle to test ordering.
Persistent Fresh FixtureFresh 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).

When to use Fresh Fixture (default)

  • Unit / integration tests with fast setup.
  • Tests that mutate state (Shared Fixture would leak across tests).
  • Anything parallel-executed.

When to use Shared Fixture

  • E2E tests where setup is genuinely expensive (multi-service stack, large seed data).
  • Tests that only read the fixture (no mutation).
  • The fixture is documented as immutable and the team enforces it.

Anti-patterns

Anti-patternWhy it fails
Shared Fixture that some tests quietly mutateCross-test coupling; failures depend on test order
Fresh Fixture for a 30-minute E2E seedTest suite time becomes infeasible; team starts skipping tests
Multiple fixture flavours in the same suite without explicit conventionEngineers can't tell what to write; bugs creep in
Fixture inheritance hierarchies >2 levels deepDepth-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

  • Output is structured and large (HTML render, JSON response, CLI output).
  • Manual assertions would be tedious or wrong (50 fields to check).
  • Changes to output are infrequent and require explicit approval anyway.

When NOT to use Snapshot

  • Output is non-deterministic (timestamps, UUIDs, locale, PII) - sanitise first or skip the snapshot.
  • Output changes frequently - the team will rubber-stamp the snapshot update and lose the test's value.
  • The behaviour you care about is one field - write an explicit assertion.

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). 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

FlavourDefinitionWhen
MaskingReplace sensitive fields with deterministic placeholders (X*** for surnames; static fake date)Production-shape preserved; field-level reversible if needed
SynthesisGenerate fake data that statistically resembles production (length distributions, locale mix)No mapping back to production; safest
TokenisationReplace sensitive values with tokens that map back via a secured lookupRound-tripping to production (rare in QA). This is pseudonymisation: the data is still personal data under GDPR

Anti-patterns

Anti-patternWhy 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 anonymousReversible or linkable data is still personal data (Recital 26)
Anonymisation that preserves the join keysSensitive relations (who bought what) survive the anonymisation
Anonymisation in CI that doesn't anonymise in dev localEngineers have raw prod data on their laptops
Anonymisation as a one-time operationProduction data changes; anonymisation must run continuously

Pattern-selection guide

NeedPatternWhen to mix
Few-fields, default-most strategyTest Data BuilderUse Factory underneath the Builder for DB persistence
Many variants of one entityFactory with traitsCombine with Builder for the test-API surface
Small stable set of canonical objectsObject MotherGenerally legacy; consider migrating to Builder + Factory
Per-test independenceFresh FixtureAlways the default; reach for Shared only when measured slow
Read-only shared stateShared FixtureDocument immutability; one mutation kills the contract
Large structured outputSnapshot / golden-fileSee golden-file-conventions for operational details
Production-shaped privacy-safe dataAnonymisationAlways for production-sourced data; pair with synthetic-pii-generator

Cross-cutting anti-patterns

Anti-patternWhy it fails
Mixing all six patterns in one codebaseEngineers can't tell what to write; vocabulary fragments
Test data inline-literaled in tests ({ name: "Alice", id: 1 }) at scaleRefactors break 200 tests when one schema field changes
Test data setup that takes >5s per testSuite 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 keysCompliance + security breach surface

Hand-off targets

  • Configure a specific per-language tool → faker-data (Python / JS / Ruby) or synthetic-data-toolkit (FactoryBot / mimesis / Bogus).
  • Build an E2E seed dataset → seed-data-curator.
  • Generate PII (anonymised) test data → synthetic-pii-generator.
  • Snapshot / golden-file operational details → golden-file-conventions.
  • Generate negative / boundary / malicious test data → negative-test-generator, boundary-value-generator, malicious-payload-bank.
  • Cross-test isolation / fixture scope rules → test-isolation-patterns (sister catalog, in the qa-test-review plugin).
  • Object-model architecture patterns → object-model-patterns (sister catalog).

References

Workspace
testland
Visibility
Public
Created
Last updated
Source
GitHub Actions
Repository
https://github.com/testland/qaVerified
Badge
testland/test-data-patterns badge