Builder Factory Generator
Generate mimicry-js + @faker-js/faker factory builders for TypeScript types (for Vitest, Storybook,
E2E, and dev seeding). This file is the API and rules; full builder examples are in the reference.
Check CLAUDE.md for the Faker locale, builder location convention, and database type patterns
before generating.
Workflow
- Find existing builders first —
Glob for **/*.builder.ts and features/*/test/builders/*.ts;
reuse before creating.
- Analyze the type — fields, nested types, arrays, optional/Date/enum/union fields.
- Place the file — feature-specific
features/[feature]/test/builders/ or shared tests/builders/.
- Name it —
camelCase(TypeName) + "Builder", file kebab-case.builder.ts
(UserProfile → userProfileBuilder in user-profile.builder.ts). Include JSDoc with usage examples.
Builder API
Methods — .one(options?) (single), .many(count, options?) (array), .reset() (reset
sequence/unique/iterators). Options: overrides (Partial), traits (string | string[]),
postBuild ((obj) => obj, per-call).
Field generators (place as field values — see placement rules):
sequence() / sequence((n) => \prefix-${n}`)` — auto-increment.
oneOf("a", "b") — random from options; unique([...]) — each value once, throws when exhausted.
bool(), int(min?, max?) (default 1–1000), float(min?, max?) (default 0–1).
withPrev((prev?) => value) — access the previous build value.
fixed(fn) — keep a function value as-is (don't call it).
() => value — plain function, fresh each build (replaces perBuild). Static values need no wrapper.
Deterministic random — seed(value) makes oneOf/int/float/bool reproducible; getSeed() reads it.
Features (code in examples.md)
- Traits — named variants (
build({ traits: { admin: { overrides: { role: "admin" } } } })), applied
via .one({ traits: "admin" }) or an array.
- postBuild — compute fields from siblings after the object is built (e.g. a total from line items).
- Nested builders —
field: () => otherBuilder.one(); overrides deep-merge (patch one nested key
without replacing the object).
- Database types — separate Base (no id/timestamps) and Application (id + Date timestamps) builders.
Generator Placement Rules
mimicry-js generators (oneOf, sequence, bool, int, float, unique, withPrev) are
field-level descriptors resolved internally by the library. They work only when placed directly as
a field value or inside a static nested object (which mimicry-js processes recursively). They do
NOT work inside arrow functions — an arrow body is opaque to mimicry-js, which just calls it and
expects a resolved value back.
fields: {
role: oneOf("admin", "user"), // ✅ resolved by mimicry-js
profile: () => ({ theme: oneOf("light", "dark") }), // ❌ returns a descriptor, not a value
theme: () => faker.helpers.arrayElement(["light", "dark"]), // ✅ use Faker inside arrow functions
}
Inside an arrow function, swap each generator for its Faker equivalent:
| Generator |
Top-level fields |
Inside () => ... |
oneOf("a", "b") |
field: oneOf("a", "b") |
field: () => faker.helpers.arrayElement(["a", "b"]) |
int(0, 100) |
field: int(0, 100) |
field: () => faker.number.int({ min: 0, max: 100 }) |
float(0, 1) |
field: float(0, 1) |
field: () => faker.number.float({ min: 0, max: 1 }) |
bool() |
field: bool() |
field: () => faker.datatype.boolean() |
sequence() |
field: sequence() |
field: () => faker.number.int() |
Use arrow functions only when a fresh value is needed each .one() call (Faker data, nested builders).
Best Practices
.many(count) for arrays — not Array.from() / [...Array(n)].map(); it supports traits/overrides
and resets state correctly.
- Prefer
oneOf/int/float/bool over Faker at top level — they integrate with seed().
unique() over oneOf() when each value must be distinct (e.g. across a .many() call).
fixed(fn) for function-typed fields (onClick, onSubmit) so mimicry-js doesn't call them.
faker.helpers.weightedArrayElement for non-uniform distributions (Faker only).
postBuild for computed fields; compose builders with () => otherBuilder.one() rather than
deep nesting; call .reset() in beforeEach when tests depend on sequence()/unique() order.
- Always use
mimicry-js (NOT test-data-bot or Fishery). sequence() for numeric ids,
() => faker.string.uuid() for UUIDs.
1---2name: builder-factory3description: Use this skill to create typed test data builders with mimicry-js and Faker for any TypeScript interface or type. Invoke whenever the user asks to generate mock data, create test fixtures, build factory functions, produce seed data, or construct fake objects for unit tests, Storybook stories, or E2E scenarios. Also use when migrating from test-data-bot or Fishery to mimicry-js, or when the user mentions builders, factories, or typed mock/fake/test/seed data generation — even if they describe the task indirectly (e.g. "I need realistic invoices for testing" or "generate sample users for my stories").4---56# Builder Factory Generator78Generate `mimicry-js` + `@faker-js/faker` factory builders for TypeScript types (for Vitest, Storybook,9E2E, and dev seeding). This file is the API and rules; full builder examples are in the reference.1011> - [references/field-mappings.md](./references/field-mappings.md) — field type → Faker method mappings.12> - [references/examples.md](./references/examples.md) — complete builders: traits, postBuild, nested/deep-merge,13> database types, discriminated unions, composition, recursive types, seeding, `withPrev`, reset.1415Check `CLAUDE.md` for the **Faker locale**, **builder location convention**, and **database type patterns**16before generating.1718## Workflow19201. **Find existing builders first** — `Glob` for `**/*.builder.ts` and `features/*/test/builders/*.ts`;21 reuse before creating.222. **Analyze the type** — fields, nested types, arrays, optional/Date/enum/union fields.233. **Place the file** — feature-specific `features/[feature]/test/builders/` or shared `tests/builders/`.244. **Name it** — `camelCase(TypeName) + "Builder"`, file `kebab-case.builder.ts`25 (`UserProfile` → `userProfileBuilder` in `user-profile.builder.ts`). Include JSDoc with usage examples.2627## Builder API2829**Methods** — `.one(options?)` (single), `.many(count, options?)` (array), `.reset()` (reset30`sequence`/`unique`/iterators). Options: `overrides` (Partial<T>), `traits` (string | string[]),31`postBuild` ((obj) => obj, per-call).3233**Field generators** (place as field values — see placement rules):3435- `sequence()` / `sequence((n) => \`prefix-\${n}\`)` — auto-increment.36- `oneOf("a", "b")` — random from options; `unique([...])` — each value once, throws when exhausted.37- `bool()`, `int(min?, max?)` (default 1–1000), `float(min?, max?)` (default 0–1).38- `withPrev((prev?) => value)` — access the previous build value.39- `fixed(fn)` — keep a function value as-is (don't call it).40- `() => value` — plain function, fresh each build (replaces `perBuild`). Static values need no wrapper.4142**Deterministic random** — `seed(value)` makes `oneOf`/`int`/`float`/`bool` reproducible; `getSeed()` reads it.4344## Features (code in examples.md)4546- **Traits** — named variants (`build({ traits: { admin: { overrides: { role: "admin" } } } })`), applied47 via `.one({ traits: "admin" })` or an array.48- **postBuild** — compute fields from siblings after the object is built (e.g. a total from line items).49- **Nested builders** — `field: () => otherBuilder.one()`; overrides deep-merge (patch one nested key50 without replacing the object).51- **Database types** — separate Base (no id/timestamps) and Application (id + Date timestamps) builders.5253## Generator Placement Rules5455mimicry-js generators (`oneOf`, `sequence`, `bool`, `int`, `float`, `unique`, `withPrev`) are56**field-level descriptors** resolved internally by the library. They work only when placed **directly as57a field value** or **inside a static nested object** (which mimicry-js processes recursively). They do58**NOT** work inside arrow functions — an arrow body is opaque to mimicry-js, which just calls it and59expects a resolved value back.6061```typescript62fields: {63 role: oneOf("admin", "user"), // ✅ resolved by mimicry-js64 profile: () => ({ theme: oneOf("light", "dark") }), // ❌ returns a descriptor, not a value65 theme: () => faker.helpers.arrayElement(["light", "dark"]), // ✅ use Faker inside arrow functions66}67```6869Inside an arrow function, swap each generator for its Faker equivalent:7071| Generator | Top-level `fields` | Inside `() => ...` |72| ----------------- | ------------------------ | ----------------------------------------------------- |73| `oneOf("a", "b")` | `field: oneOf("a", "b")` | `field: () => faker.helpers.arrayElement(["a", "b"])` |74| `int(0, 100)` | `field: int(0, 100)` | `field: () => faker.number.int({ min: 0, max: 100 })` |75| `float(0, 1)` | `field: float(0, 1)` | `field: () => faker.number.float({ min: 0, max: 1 })` |76| `bool()` | `field: bool()` | `field: () => faker.datatype.boolean()` |77| `sequence()` | `field: sequence()` | `field: () => faker.number.int()` |7879Use arrow functions only when a fresh value is needed each `.one()` call (Faker data, nested builders).8081## Best Practices8283- **`.many(count)` for arrays** — not `Array.from()` / `[...Array(n)].map()`; it supports traits/overrides84 and resets state correctly.85- **Prefer `oneOf`/`int`/`float`/`bool` over Faker** at top level — they integrate with `seed()`.86- **`unique()` over `oneOf()`** when each value must be distinct (e.g. across a `.many()` call).87- **`fixed(fn)` for function-typed fields** (`onClick`, `onSubmit`) so mimicry-js doesn't call them.88- **`faker.helpers.weightedArrayElement`** for non-uniform distributions (Faker only).89- **`postBuild` for computed fields**; **compose** builders with `() => otherBuilder.one()` rather than90 deep nesting; **call `.reset()` in `beforeEach`** when tests depend on `sequence()`/`unique()` order.91- Always use `mimicry-js` (NOT test-data-bot or Fishery). `sequence()` for numeric ids,92 `() => faker.string.uuid()` for UUIDs.