Skip to content
Back to skills

Test Factories

ASecurity

Consolidate test data creation into factories/builders with sensible defaults plus per-test overrides — killing copy-pasted object literals and shared mutable fixtures that couple tests to each other. Use when the same literal is built inline across three or more tests, when a mutated shared fixture makes suite order matter, or when a new required field forces edits across many test files. Restructures setup only; it never changes what a test asserts.

  • 2 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added October 20, 2026
ai-agentspythongojavarubyphpkotlinc#bashtestingapi

Works with

  • api

Security analysis

A100/100

Scanned October 20, 2026

npx -y skills add adnanmokhtar/refract --skill test-factories --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Test Factories?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Test Factories
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/adnanmokhtar-test-factories/badge)](https://www.skillsdirectory.com/skills/adnanmokhtar-test-factories)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: test-factories
description: Consolidate test data creation into factories/builders with sensible defaults plus per-test overrides — killing copy-pasted object literals and shared mutable fixtures that couple tests to each other. Use when the same literal is built inline across three or more tests, when a mutated shared fixture makes suite order matter, or when a new required field forces edits across many test files. Restructures setup only; it never changes what a test asserts.
kind: skill
pack: testing
allowed-tools: [Read, Write, Edit, Grep, Glob, Bash]
---

# test-factories

Test data should be built, not copy-pasted. A test needs a `User` — it asks a factory for one, overriding only the field under test. When every test hand-writes the same 12-field object literal, one schema change breaks forty tests; when they all mutate one shared fixture, they pass in order and fail in isolation. Inline duplicated setup and a shared mutable fixture are the two anti-patterns this skill removes.

## Premise

Find real data-construction rot, no hand-waves. Every finding cites `<path:line>` for the duplicated literal or the shared fixture, names the anti-class (inline-duplication / shared-mutable / no-override / collision), and states the fix as a concrete factory/builder API — `userFactory({ role: 'admin' })`, not "add a factory". A factory that hardcodes every field with no override path is not a fix — it's the shared fixture in a trench coat. "These tests look repetitive" is not a finding; "`order.spec.ts:20-32` rebuilds the same Order literal in 6 tests, differing only in `status`" is.

## Halt conditions

- Halt on a finding without `<path:line>` for the duplicated literal or fixture, plus the count of duplicating tests.
- Halt on proposing a factory with no per-test override path — defaults-only is not a factory.
- Halt on a shared-mutable-fixture claim without demonstrating order dependence (test passes in suite order, fails when run alone / reordered).
- Halt on adding a factory for a field that must be unique (email, slug, external id) without a sequence — a constant default guarantees collisions.

## When to run

- The same object literal is constructed inline across ≥ 3 tests, differing in one or two fields.
- A `beforeEach`/`setup`/module-level fixture is mutated by tests, and suite order matters.
- Adding a required field to a model forces edits across many test files.
- New tests copy an existing test's setup block wholesale.
- Integration tests need related records (an `Order` with a `Customer` and `LineItems`) and rebuild the whole graph by hand each time.

## Adapt to the codebase

Use the stack's idiomatic factory library. Prefer the one already pinned; hand-rolled builders are correct and idiomatic in some ecosystems (Go).

| Stack | Library | Notes |
|---|---|---|
| Ruby | FactoryBot | `factory`/`trait`/`association`; `sequence` for unique fields; `build` vs `create` |
| JS / TS | Fishery + @faker-js | Fishery `Factory.define` with `sequence`; faker for realistic values; test-data-bot as an alt |
| Python | factory_boy + Faker | `SubFactory` for associations; `Sequence`/`Faker` providers; `build` vs `create` strategy |
| Java / Kotlin | Instancio, Java Faker | Instancio auto-populates + `.set(field, val)` overrides; Faker for values |
| PHP | Laravel factories | `definition()` defaults + `state()` overrides + `for()`/`has()` relations; `Sequence` |
| Go | hand-rolled builders | `NewUserBuilder().WithRole("admin").Build()`; functional options; a package counter for sequences |
| C# / .NET | Bogus, AutoFixture | Bogus `Faker<T>` rules; AutoFixture `Customize` + `Build<T>().With(...)` |

If no library is pinned and the ecosystem has a standard one, propose it and STATE the choice — don't silently add a dependency where hand-rolled builders are the local idiom.

## Procedure

1. **Inventory duplication.** Scan tests for repeated object-literal construction of the same type. Group by type; count the duplicating call sites; note which fields actually vary between them.
2. **Find shared mutable fixtures.** Locate module-level / `beforeAll` objects that tests write to. Confirm order dependence: run the suspect test in isolation and reordered — if it flips, it's polluted state, not a passing test.
3. **Design the factory.** Sensible defaults for *every* field (a valid, boring instance), plus an override map/param so a test sets only the field under test. The rule: **only the field being tested should differ from the default.** Everything else is noise the reader must ignore.
4. **Add sequences for unique fields.** Email, username, slug, external id → a monotonic sequence (`user-${n}`, `n@example.test`) so N built instances never collide.
5. **Build associations.** For related graphs, a factory builds its dependencies (`orderFactory` builds a `customer` and `lineItems` unless overridden). Allow passing an existing parent to avoid over-creating.
6. **Separate seed/reference data from per-test data.** Fixed reference rows (currencies, roles, feature flags) belong in a seed loaded once and treated as read-only; anything a test mutates or asserts identity on is built fresh per test.
7. **Keep factories in sync with the schema.** A factory that drifts from the model produces invalid objects that pass unit tests and explode at the DB boundary. Tie the default set to the current schema; a new required column means a new factory default.
8. **Migrate consumers.** Replace inline literals with factory calls; delete the shared mutable fixture; confirm tests still pass *in isolation and reordered*.

## Output

```
Test-data findings — feature/checkout-tests  (base=origin/main)

Test files scanned: 22  |  Inline-literal dup groups: 3  |  Shared mutable fixtures: 1

INLINE DUPLICATION:
  test/order.spec.ts:20-32  (+5 more sites)
    Same 11-field Order literal rebuilt in 6 tests; only `status` varies.
    Fix: orderFactory.build({ status }) — Fishery; defaults cover the other 10 fields.

SHARED MUTABLE FIXTURE (order-dependent):
  test/support/fixtures.ts:8  sharedUser
    Mutated in user_profile.spec.ts:44 (sets role='admin'); leaks into
    permissions.spec.ts which passes only when run after it.
    Proof: `vitest permissions.spec.ts` alone → FAIL.
    Fix: userFactory per test; delete sharedUser.

MISSING SEQUENCE (collision risk):
  test/factories/user.ts:12  email: 'test@example.com'  (constant)
    Building 2 users → unique-index violation.
    Fix: sequence(n => `user-${n}@example.test`).

NO OVERRIDE PATH:
  test/factories/product.ts:5  every test gets identical Product
    Add ({ ...overrides }) merge so price/name can vary per test.
```

## False positives / gotchas

- **Read-only shared fixture is fine.** A shared object that no test mutates isn't the anti-pattern — don't flag it. The defect is *mutation* + order dependence, not sharing per se.
- **Over-building associations.** A factory that eagerly creates a deep graph slows the suite and creates rows tests don't need. Build parents lazily / allow injecting an existing one.
- **`build` vs `create`.** Persisting (`create`) when the test only needs an in-memory object (`build`) is needless DB traffic. Match the strategy to what the test asserts.
- **Faker without a seed.** Random realistic values make failures non-reproducible — seed the faker in test setup, or pin the fields a test asserts on.
- **Factory drift.** A factory whose defaults predate a schema change yields objects that pass unit tests and fail at the persistence boundary. Treat the factory as part of the schema contract.
- **Trait explosion.** Twenty single-use traits are worse than an override param. Reserve named traits/states for genuinely reused variants; use overrides for one-offs.

## Boundary

- `test-doubles.md` owns **behavior** — mocks, stubs, fakes, spies: what a *collaborator* does when the SUT calls it.
- `test-factories` (this) owns **data** — how the *input objects and fixtures* the test operates on are constructed.

A test typically needs both: a factory to build the `User` and `Order` it acts on, and a fake repository (test-doubles) to stand in for the database. Building data through a mock, or scripting behavior through a factory, is using the wrong tool.

Related: `test-doubles`, `@test-engineer`, `test-strategy`.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.