Skip to main content

JavaScript Test Data Generator: Typed Factories With faker-js and Fishery

`@faker-js/faker` generates realistic values for individual fields, but faker's docs explicitly say you still need to write a factory function for objects. This guide shows how to pair faker with Fishery's typed `Factory.define<T>` so TypeScript enforces your fixture contract at compile time, and how to keep both seed and sequence in step across tests.

Libraries confirmed

@faker-js/faker requires Node.js 20 or above (guide retrieved 2026-08-01). Pair with fishery for typed Factory.define. The full faker package is over 5 MiB minified and should not ship in a production web bundle.

TypeScript type safety as the fixture contract

Fishery's Factory.define<T> makes an unknown key a compile error, not a runtime surprise. Use satisfies Channel[] to enforce union exhaustiveness. Six today-relative faker methods still drift under seed alone; pass an explicit refDate or call setDefaultRefDate. Reset both faker.seed() and rewindSequence() in beforeEach (fakerjs.dev guide, retrieved 2026-08-01).

Runnable code sample

Verified against library docs retrieved 2026-08-01. Copy into your test tree and adjust domain vocabulary as needed.

// factories/order.ts
import { faker } from '@faker-js/faker';
import { Factory } from 'fishery';

type Channel = 'web' | 'ios' | 'partner-api';

export interface Order {
  id: string;
  channel: Channel;
  customerEmail: string;
  totalCents: number;
  placedAt: Date;
}

export const orderFactory = Factory.define<Order>(({ sequence, params }) => {
  const firstName = faker.person.firstName();
  const lastName = faker.person.lastName();

  return {
    id: `order-${sequence}`,
    channel: faker.helpers.arrayElement(
      ['web', 'ios', 'partner-api'] satisfies Channel[],
    ),
    customerEmail:
      params.customerEmail ?? faker.internet.email({ firstName, lastName }),
    totalCents: faker.number.int({ min: 500, max: 250000 }),
    placedAt: faker.date.soon({ refDate: '2026-01-01T00:00:00.000Z' }),
  };
});

// order.test.ts
import { beforeEach, expect, test } from 'vitest';
import { faker } from '@faker-js/faker';
import { orderFactory } from './factories/order';

beforeEach(() => {
  faker.seed(1337);
  orderFactory.rewindSequence();
});

test('a caller supplied email survives the factory default', () => {
  const order = orderFactory.build({
    channel: 'partner-api',
    customerEmail: '[email protected]',
  });

  expect(order.channel).toBe('partner-api');
  expect(order.customerEmail).toBe('[email protected]');
});

When to use Generate-Data instead

Use in-process libraries when fixtures must live next to assertions in CI. Use thefree generatorwhen you need a downloadable file, labeled duplicates with Master ID / Duplicate Type, or exports beyond what your library emits. See thegenerator comparisonandexport formats guide.

Frequently asked questions

Why does `faker.seed()` still give me different dates?

Six methods in `@faker-js/faker` are relative to today: `faker.date.past`, `faker.date.future`, `faker.date.recent`, `faker.date.soon`, `faker.git.commitEntry`, and `faker.string.uuid({ version: 7 })`. The "Reproducible results" section of https://fakerjs.dev/guide/usage.html states that "setting a random seed is not sufficient to have reproducible results" for these methods and recommends passing an explicit `refDate` argument or calling `faker.setDefaultRefDate(...)` before your suite runs. Both were confirmed on 2026-08-01.

Should I use faker directly or wrap it in a factory?

Wrap it. The "Create complex objects" section of https://fakerjs.dev/guide/usage.html states "Faker mostly generates values for primitives" and that "if you want to create an object, you most likely need to write a factory function." The Fishery README shows why a typed wrapper adds value beyond convenience: building with an unknown key is a compile error ("Argument of type '{ foo: string; }' is not assignable to parameter of type 'Partial<User>'"), so a renamed field becomes a caught mistake rather than a silently-wrong fixture. Both observed 2026-08-01.

Can I run faker in the browser for a demo?

You can, but the "Browser" section of https://fakerjs.dev/guide/usage.html warns that the full package is "> 5 MiB" minified and "should not be deployed to a web app." The section supplies an `esm.sh` import for a quick in-browser demo, but recommends against shipping it as part of a production bundle. Observed 2026-08-01.

More JavaScript testing guides