Rules for building test data in TypeScript with mimicry-js builders: explicit values for everything a test asserts on, .one() and .many() with overrides and traits, generator placement, fixed() for functions, resetting state. Use when creating or using a builder in a test or story. Background knowledge that agents preload; not a command.
Installs into .claude/skills of the current project.
Sponsors
SSkills DirectorySkills Directory
Know which skills are safe — weekly.
SSkills DirectorySkills Directory
Is that skill safe to install?
Are you the author of Builder Rules Ts?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/janszewczyk-builder-rules-ts)
---
name: builder-rules-ts
description: "Rules for building test data in TypeScript with mimicry-js builders: explicit values for everything a test asserts on, .one() and .many() with overrides and traits, generator placement, fixed() for functions, resetting state. Use when creating or using a builder in a test or story. Background knowledge that agents preload; not a command."
user-invocable: false
---
<!-- generated by knowledge.mjs from 12 note(s) (sha256 902a189b869c); edit the notes in knowledge/, then run build -->
# Test data builder rules (TypeScript, mimicry-js)
Apply these rules whenever you create or use a builder in a TypeScript test or story.
## Always apply (critical, high)
### Place mimicry-js generators directly as field values, use Faker inside arrow functions
_critical · typescript, mimicry-js_
**Rule.** Put `oneOf`, `sequence`, `int`, `float`, `bool`, `unique` and `withPrev` directly as a field value
or inside a static nested object. Inside an arrow function, use the Faker equivalent instead, for
example `faker.helpers.arrayElement([...])` for `oneOf`.
**Why.** mimicry-js resolves generators only where it walks the `fields` object. An arrow function's body is
opaque to it, so a generator returned from one ends up in the built object as a descriptor. The
test then compares against an object, not a string, and the failure message points nowhere near
the builder.
**Incorrect:**
```ts
fields: {
profile: () => ({ theme: oneOf("light", "dark") }),
}
```
**Correct:**
```ts
fields: {
theme: oneOf("light", "dark"),
profile: () => ({ theme: faker.helpers.arrayElement(["light", "dark"]) }),
}
```
### Set every value the test asserts on explicitly when you build test data
_high · any_
**Rule.** Build test data from a builder or factory, and pass an explicit override for every field the test
asserts on or branches on. Leave random values only in fields the test never looks at. Use named
variants (traits) for whole states such as "admin" or "inactive" instead of repeating the same
overrides in many tests.
**Why.** An assertion against a randomly generated value passes or fails depending on the seed, and a
reader cannot tell from the test which value matters. An explicit override documents the input the
test depends on and makes the failure reproducible.
### Wrap function-typed fields in fixed() so the builder does not call them
_high · typescript, mimicry-js_
**Rule.** For a field whose value is itself a function (`onClick`, `onSubmit`, a formatter), wrap the
function in `fixed(fn)`. Leave a bare arrow function only where you want a fresh value on each build.
**Why.** mimicry-js calls any function it finds as a field value, so a bare callback is replaced by its return
value and the built object has `undefined` where a handler should be.
**Incorrect:**
```ts
const buttonPropsBuilder = build<ButtonProps>({ fields: { label: "Save", onClick: () => {} } });
```
**Correct:**
```ts
const buttonPropsBuilder = build<ButtonProps>({ fields: { label: "Save", onClick: fixed(() => {}) } });
```
### Build with .one() and .many() and pass overrides or traits for asserted values
_high · typescript, mimicry-js_
**Rule.** Call `.one({ overrides })` for a single object and `.many(count, { overrides })` for a list.
Define recurring states as `traits` in the builder and apply them with `.one({ traits: "admin" })`.
The builder methods are `.one()` and `.many()`, not `.build()` or `.buildMany()`.
**Why.** Hand-written objects drift from the type, and `Array.from()` loops skip traits and overrides.
Calling methods that do not exist on a mimicry-js builder is the drift this note was written to
stop.
**Incorrect:**
```ts
const user = userBuilder.build();
const users = Array.from({ length: 3 }, () => userBuilder.build());
await expect(canvas.getByText(user.name)).toBeVisible();
```
**Correct:**
```ts
const user = userBuilder.one({ overrides: { name: "Ada Lovelace" } });
const admins = userBuilder.many(3, { traits: "admin" });
await expect(canvas.getByText("Ada Lovelace")).toBeVisible();
```
### Write TypeScript builders with mimicry-js and Faker
_high · typescript, mimicry-js_
**Rule.** Write every TypeScript builder with `build<T>()` from `mimicry-js`, with `@faker-js/faker` for
realistic values. Name it `camelCase(TypeName) + "Builder"` in a `kebab-case.builder.ts` file, and
migrate test-data-bot or Fishery builders when you touch them.
**Why.** Two builder libraries in one codebase mean two APIs for overrides and traits, and tests copied from
one style break when pasted next to the other.
**Incorrect:**
```ts
export const makeUser = (overrides: Partial<User> = {}): User => ({ id: "1", name: "Ada", ...overrides });
```
**Correct:**
```ts
export const userBuilder = build<User>({
fields: { id: () => faker.string.uuid(), name: () => faker.person.fullName() },
});
```
## Read when the task touches it
| Impact | Rule | Applies to |
| --- | --- | --- |
| medium | [Reset builder counters and random state between tests that depend on them](references/rules.md#reset-builder-counters-and-random-state-between-tests-that-depend-on-them) — Sequences and seeded randomness carry over between tests; reset them where a test depends on their values. | any |
| medium | [Build structured test data with a builder, not inline object literals](references/rules.md#build-structured-test-data-with-a-builder-not-inline-object-literals) — Typed objects with three or more fields come from a shared builder; reuse an existing one before writing one. | any |
| medium | [Fill nested typed objects from their own builder](references/rules.md#fill-nested-typed-objects-from-their-own-builder) — A nested type gets its own builder, used as () => otherBuilder.one(); overrides deep-merge into it. | typescript, mimicry-js |
| medium | [Build lists with .many(count), not Array.from or map](references/rules.md#build-lists-with-manycount-not-arrayfrom-or-map) — .many(n) applies overrides and traits to each item and keeps sequence and unique state right. | typescript, mimicry-js |
| medium | [Compute fields that derive from other fields in postBuild](references/rules.md#compute-fields-that-derive-from-other-fields-in-postbuild) — Totals, counts and display names built from sibling fields go in postBuild, so overrides stay consistent. | typescript, mimicry-js |
| medium | [Call builder.reset() in beforeEach when a test depends on sequence values](references/rules.md#call-builderreset-in-beforeeach-when-a-test-depends-on-sequence-values) — reset() restarts sequence() and unique() counters; seed() makes oneOf, int, float and bool reproducible. | typescript, mimicry-js |
| medium | [Use unique() for values that must differ across built objects](references/rules.md#use-unique-for-values-that-must-differ-across-built-objects) — oneOf() can repeat a value within one .many() call; unique() hands each value out once. | typescript, mimicry-js |