Harness-engineering test-factory-patterns

Test Factory Patterns

install
source · Clone the upstream repo
git clone https://github.com/Intense-Visions/harness-engineering
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/Intense-Visions/harness-engineering "$T" && mkdir -p ~/.claude/skills && cp -r "$T/agents/skills/codex/test-factory-patterns" ~/.claude/skills/intense-visions-harness-engineering-test-factory-patterns-a0e26c && rm -rf "$T"
manifest: agents/skills/codex/test-factory-patterns/SKILL.md
source content

Test Factory Patterns

Build maintainable test data using factory functions, builders, and faker.js

When to Use

  • Creating test data for unit and integration tests
  • Reducing duplication in test setup across test files
  • Generating realistic fake data for testing edge cases
  • Building complex object graphs with sensible defaults

Instructions

  1. Simple factory function with defaults and overrides:
function createUser(overrides?: Partial<User>): User {
  return {
    id: crypto.randomUUID(),
    name: 'Test User',
    email: `user-${crypto.randomUUID().slice(0, 8)}@test.com`,
    role: 'user',
    createdAt: new Date(),
    ...overrides,
  };
}

// Usage
const admin = createUser({ role: 'admin', name: 'Admin' });
const alice = createUser({ name: 'Alice' });
  1. Factory with faker.js for realistic data:
import { faker } from '@faker-js/faker';

function createUser(overrides?: Partial<User>): User {
  return {
    id: faker.string.uuid(),
    name: faker.person.fullName(),
    email: faker.internet.email(),
    role: 'user',
    avatar: faker.image.avatar(),
    createdAt: faker.date.past(),
    ...overrides,
  };
}
  1. Builder pattern for complex objects:
class UserBuilder {
  private data: Partial<User> = {};

  withName(name: string): this {
    this.data.name = name;
    return this;
  }
  withRole(role: Role): this {
    this.data.role = role;
    return this;
  }
  withEmail(email: string): this {
    this.data.email = email;
    return this;
  }
  asAdmin(): this {
    this.data.role = 'admin';
    return this;
  }

  build(): User {
    return createUser(this.data);
  }
}

// Usage
const admin = new UserBuilder().withName('Alice').asAdmin().build();
  1. Factory for related entities:
function createPost(
  overrides?: Partial<Post> & { author?: Partial<User> }
): Post & { author: User } {
  const author = createUser(overrides?.author);
  return {
    id: crypto.randomUUID(),
    title: faker.lorem.sentence(),
    content: faker.lorem.paragraphs(3),
    authorId: author.id,
    published: false,
    createdAt: new Date(),
    ...overrides,
    author,
  };
}
  1. Batch factory for lists:
function createUsers(count: number, overrides?: Partial<User>): User[] {
  return Array.from({ length: count }, () => createUser(overrides));
}

const tenAdmins = createUsers(10, { role: 'admin' });
  1. Deterministic factories for snapshot testing:
faker.seed(42); // Same seed = same data every run

function createDeterministicUser(): User {
  return createUser(); // Always generates the same sequence
}
  1. Database factory for integration tests:
async function createDbUser(prisma: PrismaClient, overrides?: Partial<User>) {
  const data = createUser(overrides);
  return prisma.user.create({
    data: {
      email: data.email,
      name: data.name,
      role: data.role,
    },
  });
}

// In tests
const user = await createDbUser(prisma, { role: 'admin' });
  1. Organize factories in a central file:
// test/factories/index.ts
export { createUser, createUsers, UserBuilder } from './user-factory';
export { createPost, createPosts } from './post-factory';
export { createOrder } from './order-factory';

Details

Test factories centralize test data creation, providing consistent defaults while allowing per-test customization. They eliminate the "copy-paste test setup" anti-pattern.

Factory vs fixture vs seed:

  • Factory — creates data programmatically with overrides. Best for unit and integration tests
  • Fixture — static data loaded from files (JSON, SQL). Best for large, stable datasets
  • Seed — populates a database for development. Factories can be used inside seeds

Faker.js tips:

  • faker.seed(n)
    makes all subsequent calls deterministic — use for reproducible tests
  • faker.helpers.arrayElement(['a', 'b', 'c'])
    picks randomly from a list
  • faker.helpers.multiple(fn, { count: 5 })
    generates multiple values

Factory design principles:

  • Default values should produce a valid object (passes all validation)
  • Override only what matters for the test — defaults handle the rest
  • Unique values (email, ID) should be generated, not hardcoded — prevents constraint violations
  • Related objects should be creatable together (post + author)

Trade-offs:

  • Factories reduce test setup duplication — but add a maintenance surface (factory changes can break many tests)
  • Faker generates realistic data — but can produce edge cases unexpectedly (very long names, unicode characters)
  • Builders are expressive — but verbose for simple objects. Use plain factories for objects with fewer than 5 fields
  • Deterministic seeds help snapshots — but hide randomized testing benefits

Source

https://vitest.dev/guide/

Process

  1. Read the instructions and examples in this document.
  2. Apply the patterns to your implementation, adapting to your specific context.
  3. Verify your implementation against the details and edge cases listed above.

Harness Integration

  • Type: knowledge — this skill is a reference document, not a procedural workflow.
  • No tools or state — consumed as context by other skills and agents.

Success Criteria

  • The patterns described in this document are applied correctly in the implementation.
  • Edge cases and anti-patterns listed in this document are avoided.