Test Data Factories skill for Claude Code
Valid test data by default, only relevant fields shown. Claude replaces sprawling object literals and shared fixtures with one factory per entity that builds a valid object from zero arguments, uses sequences for unique fields, and exposes named traits. Tests show only the fields that drive the assertion, Faker is seeded for replay, and seed scripts or masked production subsets are built without copying real PII.
- name
test-data-factories - size200 lines · ≈1,850 tokens
- files1
- licenseMIT
- updated
What Claude sees before loading it
This description is the only part of the skill that sits in context all the time. Claude reads it to decide when to load the full playbook below.
description: Designs test data with factories and builders (fishery, factory_boy, FactoryBot, hand-rolled builders) that produce valid objects by default, expose only the fields a test cares about, handle sequences, associations and traits, and stay deterministic with seeded Faker. Also covers idempotent dev/staging seed scripts and building anonymized, production-like datasets without leaking PII. Use when tests repeat large object literals, when shared fixtures break unrelated tests, when unique constraint errors appear in tests, or when creating seed data or masked copies of production. Try asking
- Our tests have 50-line user objects everywhere. Create factories and clean them up
- Write an idempotent seed script for local development with a few personas
- We keep getting unique constraint errors when tests run in parallel
Use it when
- Tests repeat large inline objects or share static fixtures
- Unique constraint or ordering errors appear in parallel test runs
- Creating dev or staging seed data
- Building an anonymized, production-like dataset
Skip it when
- You need randomized inputs to find edge cases: use property-based-testing
- You are designing the database schema itself: use postgres-schema-design
How to check the result
Every factory builds a valid object with no arguments, the suite passes in parallel and shuffled, and masked datasets pass a PII scan before anyone uses them.
The common mistake it prevents
Letting a test silently depend on a factory default value, so changing the default breaks unrelated tests far away.
Good test data is valid by default, minimal in each test, unique where the schema needs uniqueness, and reproducible. A reader should see in the test exactly which fields drive the outcome; everything else comes from the factory.
Ground rules
- One factory per domain entity, producing a valid object with zero arguments.
- Tests override only the fields that matter to the assertion. If a test depends on a default value, set that value explicitly in the test.
- Unique fields use sequences, never random values alone (randomness collides eventually and is not reproducible).
- Prefer
build(in memory) overcreate(persisted). Persist only when the code under test reads from the database. - Factories create the minimum graph. Associations are created lazily or on request, not five levels deep by default.
- Never copy raw production data into tests or dev environments.
Workflow
- [ ] 1. Inventory entities, required fields, unique constraints, relationships
- [ ] 2. Write one factory per entity with valid defaults and sequences
- [ ] 3. Add traits for common named states (admin, archived, overdue)
- [ ] 4. Replace inline literals and shared fixtures in tests
- [ ] 5. Seed Faker deterministically and log the seed
- [ ] 6. Build seed scripts or masked datasets for non-test environments
2–3. Factories by ecosystem
TypeScript (fishery):
import { Factory } from 'fishery';
import { faker } from '@faker-js/faker';
class UserFactory extends Factory<User> {
admin() { return this.params({ role: 'admin' }); }
suspended() { return this.params({ status: 'suspended', suspendedAt: new Date('2026-01-01T00:00:00Z') }); }
}
export const userFactory = UserFactory.define(({ sequence, onCreate }) => {
onCreate((user) => db.user.create({ data: user }));
return {
id: `usr_${sequence}`,
email: `user${sequence}@example.test`,
name: faker.person.fullName(),
role: 'member',
status: 'active',
createdAt: new Date('2026-01-01T00:00:00Z'),
};
});
// usage
const admin = userFactory.admin().build({ name: 'Ada' });
const members = userFactory.buildList(3);
const persisted = await userFactory.create();
Python (factory_boy):
import factory
from factory.django import DjangoModelFactory # or factory.alchemy.SQLAlchemyModelFactory
class UserFactory(DjangoModelFactory):
class Meta:
model = User
email = factory.Sequence(lambda n: f"user{n}@example.test")
name = factory.Faker("name")
role = "member"
team = factory.SubFactory(TeamFactory)
class Params:
admin = factory.Trait(role="admin", is_staff=True)
# usage
UserFactory.build(name="Ada") # unsaved
UserFactory(admin=True) # saved, with trait
UserFactory.create_batch(3, team=team)
Ruby (FactoryBot):
FactoryBot.define do
factory :user do
sequence(:email) { |n| "user#{n}@example.test" }
name { "Test User" }
role { "member" }
trait :admin do
role { "admin" }
end
end
end
build_stubbed(:user) # fastest; no DB
create(:user, :admin)
Builders without a library work well for value objects and small codebases:
export const anOrder = (overrides: Partial<Order> = {}): Order => ({
id: 'ord_1', currency: 'USD', items: [anItem()], status: 'pending', ...overrides,
});
Name builders aX / anX so tests read naturally: applyCoupon(anOrder({ items: [] }), aCoupon()).
Associations
| Need | Approach |
|---|---|
| Test reads only the child | Build the child with a parent ID, no parent object |
| Test needs the parent loaded | SubFactory / associations built on demand |
| Many children share one parent | Create the parent once in the test and pass it in |
| Deep graphs (org → team → project → task) | Explicit helper per scenario (givenProjectWithTasks(3)), not default cascades |
A default that silently creates an organization, a billing account, and three users per
create() makes every test slow and every failure confusing.
4. Replace fixtures
Shared static fixtures (fixtures/users.json, a seeded user1 used by 80 tests) cause
mystery-guest tests and cross-test coupling. Migrate incrementally: when touching a test,
replace its fixture reference with a factory call that sets the relevant fields.
5. Determinism
// test setup
import { faker } from '@faker-js/faker';
const seed = Number(process.env.TEST_SEED ?? Date.now() % 100000);
faker.seed(seed);
console.log(`faker seed: ${seed}`); // replay with TEST_SEED=<seed>
factory_boy: factory.random.reseed_random(seed). Faker values belong in fields that do
not affect assertions; anything an assertion depends on is set explicitly.
Fixed timestamps beat new Date() in factories. If time matters, take it from an injected
clock.
6. Seed data and production-like datasets
Dev/staging seed scripts:
- Idempotent: upsert by natural key so running twice changes nothing.
- Built from the same factories, so seeds stay valid as the schema evolves.
- Include named personas for manual testing (
[email protected],[email protected]), plus volume data for performance-sensitive screens. - Run from a single command (
npm run db:seed,python manage.py seed, or the ORM’s seed hook, e.g.npx prisma db seed).
Anonymized production-like data (for realistic volume and distribution):
- Get explicit approval from the data owner; document the purpose and retention.
- Subset first (e.g., 1% of tenants with referential integrity preserved).
- Mask every PII column by rule: emails →
user{id}@example.test, names → Faker with a deterministic seed per row, phones → fictional ranges, addresses → generated, free-text fields → replaced entirely (they contain PII in unpredictable places). - Keep shapes that matter for testing: string lengths, null rates, distributions, edge characters (Unicode names), foreign key cardinality.
- Run masking inside the trusted environment (tools like PostgreSQL Anonymizer or Greenmask, or a scripted transform) so unmasked data never leaves it.
- Verify: scan the output for real email domains, phone patterns, and known VIP names before distributing.
Use reserved values: example.com/example.test domains, 555-01xx style fictional
phone numbers, test card numbers from the payment provider’s docs.
Gotchas
- Unique constraint failures in parallel test workers: sequences are per process; prefix with the worker ID or use UUIDs plus a sequence.
- Factories that call the API or services under test create circular dependencies; write directly to the DB or build in memory.
- Faker locale changes or upgrades change generated values; tests asserting on Faker output break. Assert only on explicit values.
buildobjects missing DB defaults (timestamps, generated IDs) cause false passes; set them in the factory.- Traits that conflict (
archived+active) should raise, not silently pick one.
Verification
- Every factory builds a valid object with no arguments; add a test per factory that validates against the schema or saves it.
- Run the suite in parallel and shuffled: no unique-constraint or order failures.
- Grep tests for large inline object literals and shared fixture IDs that remain.
- For masked datasets, run the PII scan and record the result.
About this skill
Written and maintained by the Claude Skills editors to the same editorial standard as every skill in the directory: a selective trigger, a real workflow, known pitfalls and a verification step. First published , last updated . MIT licensed.