From harness-claude
Establishes patterns for test factories, fixtures, database seeding, and test data isolation to create realistic, composable data without coupling tests to database states.
npx claudepluginhub intense-visions/harness-engineering --plugin harness-claudeThis skill uses the workspace's default tool permissions.
> Test factories, fixtures, database seeding, and test data isolation. Establishes patterns for creating realistic, composable test data without coupling tests to specific database states.
Generates realistic test data, fixtures, factories, seeds, and edge cases using Faker.js, Fishery, pytest fixtures for JS/TS/Python apps and databases.
Manages Rails development and test data with seeds, fixtures, FactoryBot factories, idempotent patterns, and environment-specific strategies.
Share bugs, ideas, or general feedback.
Test factories, fixtures, database seeding, and test data isolation. Establishes patterns for creating realistic, composable test data without coupling tests to specific database states.
Catalog domain models. Scan for:
Map model relationships. For each model, identify:
Inventory existing test data patterns. Search for:
Identify test data problems. Flag:
Report findings. Summarize: models found, existing patterns, and specific problems to address.
Select the factory pattern. Based on the project's language and conventions:
Design the factory API. Each factory must support:
UserFactory.build() returns a valid object with sensible defaultsUserFactory.build({ name: 'Custom' }) overrides specific fieldsUserFactory.build({ trait: 'admin' }) applies a named set of overridesProjectFactory.build() automatically creates a related User ownerUserFactory.buildList(5) returns an arrayPlan data relationships. Define how factories handle foreign keys:
Design cleanup strategy. Choose based on test infrastructure:
Define seed data tiers. Separate:
Create the factory directory structure. Follow the project's conventions:
tests/factories/ or src/__tests__/factories/ for unit/integration test factoriesseeds/ or prisma/seed.ts for database seeding scriptstests/fixtures/ for static fixture data (JSON, YAML)Generate a factory for each domain model. Each factory file contains:
Generate a factory index. Create a barrel file that exports all factories for easy importing:
import { UserFactory, ProjectFactory, TaskFactory } from '../factories';
Create seed scripts. Generate:
Create cleanup utilities. Generate:
Verify factories produce valid data. Write a smoke test that builds one instance of every factory and validates it against the model schema.
Test factory defaults. For each factory, verify:
build() returns a valid object that passes model validationTest factory composition. Verify:
UserFactory.build({ traits: ['admin', 'verified'] }) applies bothTest data isolation. Run the test suite with factory-generated data and verify:
Test seed scripts. Verify:
Run harness validate. Confirm the project passes all harness checks with factory infrastructure in place.
If a knowledge graph exists at .harness/graph/, refresh it after code changes to keep graph queries accurate:
harness scan [path]
harness validate -- Run in VALIDATE phase after all factories and seed scripts are created. Confirms project health.harness check-deps -- Run after SCAFFOLD phase to ensure test factory dependencies (Faker, fishery) are in devDependencies, not dependencies.emit_interaction -- Used at design checkpoints to present factory pattern options and cleanup strategy choices to the human.harness validate passes with factory infrastructure in placeSCAFFOLD -- User factory with traits:
// tests/factories/user.factory.ts
import { Factory } from 'fishery';
import { faker } from '@faker-js/faker';
import { User } from '../../src/types/user';
export const UserFactory = Factory.define<User>(({ sequence, params, transientParams }) => ({
id: `user-${sequence}`,
email: params.email ?? faker.internet.email(),
name: params.name ?? faker.person.fullName(),
role: params.role ?? 'member',
status: params.status ?? 'active',
createdAt: params.createdAt ?? faker.date.past(),
updatedAt: new Date(),
}));
// Traits
export const AdminUser = UserFactory.params({ role: 'admin' });
export const InactiveUser = UserFactory.params({ status: 'inactive' });
SCAFFOLD -- Project factory with association:
// tests/factories/project.factory.ts
import { Factory } from 'fishery';
import { faker } from '@faker-js/faker';
import { Project } from '../../src/types/project';
import { UserFactory } from './user.factory';
export const ProjectFactory = Factory.define<Project>(({ sequence, associations }) => ({
id: `project-${sequence}`,
name: faker.commerce.productName(),
description: faker.lorem.sentence(),
owner: associations.owner ?? UserFactory.build(),
ownerId: associations.owner?.id ?? `user-${sequence}`,
status: 'active',
createdAt: faker.date.past(),
}));
SCAFFOLD -- Django model factories:
# tests/factories.py
import factory
from factory.django import DjangoModelFactory
from faker import Faker
from myapp.models import User, Organization, Project
fake = Faker()
class OrganizationFactory(DjangoModelFactory):
class Meta:
model = Organization
name = factory.LazyFunction(lambda: fake.company())
slug = factory.LazyAttribute(lambda o: o.name.lower().replace(' ', '-'))
class UserFactory(DjangoModelFactory):
class Meta:
model = User
email = factory.LazyFunction(lambda: fake.unique.email())
name = factory.LazyFunction(lambda: fake.name())
organization = factory.SubFactory(OrganizationFactory)
class Params:
admin = factory.Trait(is_staff=True, is_superuser=True)
class ProjectFactory(DjangoModelFactory):
class Meta:
model = Project
name = factory.LazyFunction(lambda: fake.catch_phrase())
owner = factory.SubFactory(UserFactory)
organization = factory.LazyAttribute(lambda p: p.owner.organization)
Usage in tests:
def test_project_belongs_to_owner_organization():
project = ProjectFactory.create()
assert project.organization == project.owner.organization
def test_admin_can_delete_any_project():
admin = UserFactory.create(admin=True)
project = ProjectFactory.create()
assert admin.has_perm('delete_project', project)
| Rationalization | Reality |
|---|---|
"The test only needs one user — I'll just hardcode userId: 1 rather than building a factory." | Hardcoded IDs cause silent test failures when the database is reset, when tests run in parallel, or when another test creates a conflicting record. Factories with sequences or UUIDs exist precisely to avoid this class of failure. One hardcoded ID is how fragile test suites start. |
| "The factory produces objects that are close enough to valid — the test just needs to override two fields anyway." | A factory that requires overrides to produce a valid object has wrong defaults. The factory's zero-override output must pass model validation. If it does not, the factory is documenting the wrong defaults and tests that rely on overrides will break when the model changes. |
| "Cleanup is handled by rolling back the test transaction — I don't need explicit teardown." | Transaction rollback works until it does not: tests that span multiple connections, tests that call external APIs, or tests that write to a queue or file system all escape the transaction. Explicit cleanup is the only strategy that covers all cases, including tests that use features you have not built yet. |
| "We can share the same seeded dataset across all integration tests to avoid the overhead of per-test factories." | Shared mutable data means test A's side effect becomes test B's precondition. When tests fail intermittently based on execution order, the root cause is always shared state. The overhead of per-test factory creation is small compared to the cost of debugging order-dependent failures. |
| "The model only has three fields — writing a factory is more overhead than just constructing the object inline." | Today's three-field model becomes tomorrow's ten-field model with required foreign keys. Inline construction scales linearly with model complexity. A factory written once absorbs all future field additions in one place. The overhead argument inverts as the codebase grows. |
build() with zero overrides must return an object that passes model validation. If it requires manual overrides to be valid, the defaults are wrong.