Start simple, scale up
One value, one object, or a whole dataset — adopt only what your test needs.
Composable, type-safe builders for generating related mock data.
Click ▶ Run to see example output. Click again for a different sample.
import { numeric, person, text, temporal, choice } from 'storymock';
const age = numeric().min(18).max(65).create(); // 34
const name = person().fullName().create(); // "Yuki Tanaka"
const id = text().uuid().create(); // "7b3e1d09-a4c2-..."
const born = temporal().past(50).iso().create(); // "2001-03-14T08:23:41.000Z"
const status = choice('active', 'inactive').create(); // "active" or "inactive"Each layer builds on the one below. Use any layer on its own — or combine them for full relational datasets.
numeric().min(1).max(100) · person().fullName() · text().uuid()
.min() · .max() · .precision() · .not() · .unique()
schema<User>({ id: text().uuid(), name: person().fullName() }).trait('admin', { ... })
.trait() · when() · derive() · .id()
story().add('user', UserSchema).add('order', OrderSchema, { userId: ref('user') })
ref() · .setup() · .with() · .addMany()
Without storymock
// Every test re-specifies the same states manually
const expiredAdmin = createUser({
role: 'admin',
status: 'active',
subscription: {
plan: 'enterprise',
expiresAt: new Date('2024-01-01'),
status: 'expired',
},
});
const org = createOrg({ ownerId: expiredAdmin.id });
const team = createTeam({ orgId: org.id });
const members = Array.from({ length: 5 }, (_, i) =>
createUser({
teamId: team.id,
role: i === 0 ? 'admin' : 'member',
})
);
team.memberIds = members.map(m => m.id);
org.teamIds = [team.id];
// Next test — same setup, slightly different state.
// Copy, paste, tweak, hope nothing drifts.With storymock
// Schemas define realistic defaults + named states once
const UserSchema = schema<User>({
id: text().uuid(),
name: person().fullName(),
role: choice('admin', 'member'),
subscription: SubscriptionSchema,
}).trait('expiredAdmin', {
role: 'admin' as const,
subscription: SubscriptionSchema.with('expired'),
});
// Stories compose objects and wire relationships
const orgStory = story()
.add('owner', UserSchema.with('expiredAdmin'))
.add('org', OrgSchema, { ownerId: ref('owner') })
.addMany('members', UserSchema, 5)
.setup(wireTeamMembers);
// Tests only express what's different
orgStory.create();
orgStory.with('owner', 'active').create();
orgStory.with('members[0]', 'admin').create();Define 'expiredAdmin' or 'premium' once. Apply by name — no copy-pasting field values.
person().fullName(), temporal().past(), finance().amount().precision(2) — built-in and composable.
ref() wires foreign keys. .setup() distributes members. One .create() gives you the whole graph.