@ferrow/fixture-factory
v1.0.0
Published
Seeded, deterministic test fixture builder: sequences, pick, factory refs, traits, and afterBuild hooks. Zero runtime dependencies.
Maintainers
Readme
fixture-factory
Seeded, deterministic test fixture builder: sequences, pick, factory refs, composable traits, and afterBuild hooks. Strict TypeScript, zero runtime dependencies.
Why
Flaky fixtures make flaky tests. This factory library is deterministic by construction — a
mulberry32 PRNG seeded at define() time means the same seed and template always produce the same
fixture sequence, so failures are reproducible and snapshot tests stay stable.
Quickstart
import { define, sequence, pick, ref } from "fixture-factory";
const userFactory = define(
"user",
{
id: sequence((i) => `user-${i}`),
name: pick(["Alice", "Bob", "Cara"]),
role: "member",
},
{ seed: 42 }
).trait("admin", { role: "admin" });
const postFactory = define("post", {
id: sequence((i) => `post-${i}`),
author: ref(userFactory),
published: false,
}).trait("published", { published: true });
const post = postFactory.build(undefined, { traits: ["published"] });
const admin = userFactory.build(undefined, { traits: ["admin"] });API
define(name, template, options?): Factory
options.seed (default 0) seeds the factory's internal PRNG.
Template values
- Literals — used as-is.
sequence(fn)—fn(index), whereindexis a 1-based counter incremented once perbuild().pick(values)— seeded-random selection fromvalues.ref(factory, overrides?, maxDepth?)— builds a related fixture via another factory. Ref chains deeper thanmaxDepth(default3) resolve tonullinstead of recursing forever.- Functions —
(ctx) => value, given{ rng, sequenceIndex, depth }for custom logic.
Factory.trait(name, partialTemplate)
Registers a named partial template. Composable — pass multiple trait names to build/buildList
and they apply in order, each overriding the fields it specifies.
Factory.afterBuild(hook)
Runs hook(built, ctx) after every build; return a value to replace the fixture, or mutate and
return undefined.
Factory.build(overrides?, options?) / Factory.buildList(n, overrides?, options?)
options.traits selects which registered traits to apply.
Limits
- Determinism holds only across separately-defined factories with the same seed and the same
sequence of build calls — reordering or adding extra
build()calls between two runs will desync their PRNG/sequence state. ref()depth limiting returnsnullpast the limit rather than throwing — check fornullin your templates if a ref chain could realistically be circular.- No async factories — build functions run synchronously.
Part of the ferrow-toolkit collection · Sponsored by Ferrow
