Skip to content

Gityosan/zod-v4-mocks

Repository files navigation

zod-v4-mocks

Mock generator for zod v4

Note: If you're using [email protected] with import from 'zod/v4' (preview), please use zod-v4-preview-mocks instead. It is compatible with [email protected].

Version Guide

This library supports Zod v4.0.0+ only (v2.x).

Important: v2.0.0–v2.0.4 had compatibility issues with Zod versions older than v4.3.5. If you use Zod v4.0.0–v4.3.4, please upgrade to v2.1.0+ which properly supports all Zod v4.0.0+ versions.

For Zod v3.25.76 (v4 preview), use zod-v4-preview-mocks.

Installation

npm install zod-v4-mocks

Basic Usage

import { initGenerator } from 'zod-v4-mocks';
import { z } from 'zod';

const schema = z.string();

const generator = initGenerator({ seed: 1 });
const res = generator.generate(schema);
console.log(schema.parse(res));

Usage Details

1. Basic Usage

The simplest way to use the library. Generate mock data from a schema.

import { z } from 'zod';
import { initGenerator } from 'zod-v4-mocks';

const basicSchema = z.object({
  id: z.uuid(),
  name: z.string(),
  email: z.email(),
  age: z.number().min(18).max(120).optional(),
  isActive: z.boolean(),
  tags: z.array(z.string()),
  createdAt: z.date(),
});

const mockUser = initGenerator().generate(basicSchema);

2. Custom Configuration

You can customize settings such as seed value, array length, and locale.

const config = {
  seed: 42,
  array: { min: 2, max: 5 },
  locale: 'ja',
} as const;

const mockUserWithConfig = initGenerator(config).generate(basicSchema);

The available configuration options are as follows:

interface MockConfig {
  /**
   * @default [en, base] from faker.js
   */
  locale?: LocaleType | LocaleType[];
  /**
   * @default generateMersenne53Randomizer() from faker.js
   */
  randomizer?: Randomizer;
  /**
   * @default 1
   */
  seed: number;
  /**
   * @default { min: 1, max: 3 }
   */
  array: { min: number; max: number };
  /**
   * @default { min: 1, max: 3 }
   */
  map: { min: number; max: number };
  /**
   * @default { min: 1, max: 3 }
   */
  set: { min: number; max: number };
  /**
   * @default { min: 1, max: 3 }
   */
  record: { min: number; max: number };
  /**
   * @default 0.5
   */
  optionalProbability: number;
  /**
   * @default 0.5
   */
  nullableProbability: number;
  /**
   * @default 0.5
   */
  defaultProbability: number;
  /**
   * @default 5
   */
  lazyDepthLimit: number;
  /**
   * @description meta's attribute name which is used to generate consistent property value
   */
  consistentKey?: string;
}

3. Supply Specific Values (as Custom Generator)

You can supply fixed values for specific schema types.

const mockUserWithSupply = initGenerator()
  .supply(z.ZodString, 'custom name')
  .supply(z.ZodEmail, '[email protected]')
  .generate(basicSchema);

If you configure multiple values for the same ZodType condition, the first configured value takes priority.

const mockUserWithSupply = initGenerator()
  .supply(z.ZodEmail, '[email protected]')
  .supply(z.ZodEmail, '[email protected]')
  .generate(basicSchema);

4. Override mock generator (as Custom Generator)

You can define custom generator functions for specific schemas.

import { type CustomGeneratorType } from 'zod-v4-mocks';

const customGenerator: CustomGeneratorType = (schema, options) => {
  const { faker } = options;
  if (schema instanceof z.ZodString && schema === basicSchema.shape.name) {
    return 'custom name: ' + faker.person.fullName();
  }
};

const mockUserWithOverride = initGenerator()
  .override(customGenerator)
  .generate(basicSchema);

If you configure multiple values for the same ZodType condition, the first configured value takes priority.

const customGenerator: CustomGeneratorType = (schema, options) => {
  const { faker } = options;
  if (schema instanceof z.ZodEmail) {
    return '[email protected]';
  }
};

const mockUserWithSupply = initGenerator()
  .supply(z.ZodEmail, '[email protected]')
  .override(customGenerator)
  .generate(basicSchema);

5. Complex Schemas

Supports complex schemas including nested objects, arrays, records, and unions.

const complexSchema = z.object({
  user: z.object({
    profile: z.object({
      name: z.string(),
      bio: z.string().optional(),
    }),
    preferences: z.object({
      theme: z.enum(['light', 'dark', 'auto']),
      notifications: z.boolean().default(true),
    }),
  }),
  posts: z.array(
    z.object({
      title: z.string(),
      content: z.string(),
      publishedAt: z.date().nullable(),
      tags: z.array(z.string()),
    }),
  ),
  metadata: z.record(
    z.string(),
    z.union([z.string(), z.number(), z.boolean()]),
  ),
});

const complexMock = initGenerator().generate(complexSchema);

6. Consistent Data Generation (Register)

You can generate consistent data between related fields using metadata.

// Set the metadata key name
const consistentKey = 'name';
const UserId = z.uuid().meta({ [consistentKey]: 'UserId' });
const CommentId = z.uuid().meta({ [consistentKey]: 'CommentId' });
const PostId = z.uuid().meta({ [consistentKey]: 'PostId' });

const userSchema = z.object({
  id: UserId, // 1: Same UserId will be generated
  name: z.string(),
});

const commentSchema = z.object({
  id: CommentId,
  postId: PostId, // 2: Same PostId will be generated
  user: userSchema,
  userId: UserId, // 1: Same UserId will be generated
  value: z.string(),
});

const postSchema = z.object({
  id: PostId, // 2: Same PostId will be generated
  comments: z.array(commentSchema),
  value: z.string(),
});

const PostsResponse = z.array(postSchema);

// Register schemas first, then generate
const schemas = [CommentId, UserId, PostId];
const mock = initGenerator({ consistentKey })
  .register(schemas)
  .generate(PostsResponse);

This generator will generate mock like below.

[
  {
    "id": "08e93b6a-0a0b-4718-81af-c91ba0c86c67",
    "comments": [
      {
        "id": "b438b6fa-765b-4706-8b22-88adb9b5534a",
        "postId": "08e93b6a-0a0b-4718-81af-c91ba0c86c67",
        "user": {
          "id": "c9b26358-a125-4ad8-ad65-52d58980fe34",
          "name": "acceptus"
        },
        "userId": "c9b26358-a125-4ad8-ad65-52d58980fe34",
        "value": "antepono"
      },
      {
        "id": "667646ef-8915-46d4-a7f8-91d98546ae8a",
        "postId": "08e93b6a-0a0b-4718-81af-c91ba0c86c67",
        "user": {
          "id": "ef49c6b2-9df1-4637-a814-e97cb0bd2c44",
          "name": "tergiversatio"
        },
        "userId": "ef49c6b2-9df1-4637-a814-e97cb0bd2c44",
        "value": "adipisci"
      },
      {
        "id": "9ad1c339-1eab-4098-922b-5b86ed5046bf",
        "postId": "08e93b6a-0a0b-4718-81af-c91ba0c86c67",
        "user": {
          "id": "1aeb77c9-19d5-4ec9-bc1d-de21266813c7",
          "name": "sum"
        },
        "userId": "1aeb77c9-19d5-4ec9-bc1d-de21266813c7",
        "value": "volup"
      }
    ],
    "value": "ut"
  },
  {
    "id": "5d26f3ee-1785-4031-93ce-04f8a07f617f",
    "comments": [
      {
        "id": "b438b6fa-765b-4706-8b22-88adb9b5534a",
        "postId": "5d26f3ee-1785-4031-93ce-04f8a07f617f",
        "user": {
          "id": "c9b26358-a125-4ad8-ad65-52d58980fe34",
          "name": "vitae"
        },
        "userId": "c9b26358-a125-4ad8-ad65-52d58980fe34",
        "value": "curtus"
      },
      {
        "id": "667646ef-8915-46d4-a7f8-91d98546ae8a",
        "postId": "5d26f3ee-1785-4031-93ce-04f8a07f617f",
        "user": {
          "id": "ef49c6b2-9df1-4637-a814-e97cb0bd2c44",
          "name": "uterque"
        },
        "userId": "ef49c6b2-9df1-4637-a814-e97cb0bd2c44",
        "value": "constans"
      },
      {
        "id": "9ad1c339-1eab-4098-922b-5b86ed5046bf",
        "postId": "5d26f3ee-1785-4031-93ce-04f8a07f617f",
        "user": {
          "id": "1aeb77c9-19d5-4ec9-bc1d-de21266813c7",
          "name": "vetus"
        },
        "userId": "1aeb77c9-19d5-4ec9-bc1d-de21266813c7",
        "value": "arcesso"
      }
    ],
    "value": "vilicus"
  }
]

Unsupported Schemas

  • z.ZodCustom
    • .custom() and .instanceof() are not supported
  • .catchall() is ignored
  • .function() is not supported
  • .nativeEnum() is deprecated in v4
  • .promise() is deprecated in v4
  • .superRefine() is deprecated in v4

Partially Supported Schemas

  • z.ZodLazy is partially supported
    • If schema has toplevel z.union, this library would throw error
    • If schema has z.tuple or z.intersection anywhere, this library would cause RangeError
  • z.ZodIntersection is partially supported
    • Same schema types are basically supported
      • But, if the elements of the Map/Set/Array/Enum/Union do not have compatible elements, then this library would throw error.
    • Different schema types are not supported in principle
      • But, ZodObject and ZodRecord would be successfully generated
      • But, ZodArray and ZodTuple would be successfully generated
    • If one element type is ZodAny/ZodUnknown, the other element type is used
  • z.ZodEffects (transform/refine/preprocess/check) is partially supported
    • .transform() and .preprocess() are fully supported
    • .refine() validation conditions are ignored during mock generation
    • .check() function is mostly NOT supported:
      • z.string().check(z.regex(/pattern/)) - ❌ NOT supported
      • z.string().check(z.minLength(10)) - ❌ NOT supported
      • z.string().check(z.overwrite(...)) - ✅ Supported (only exception)
      • z.string().check(z.trim()) - ✅ Supported (overwrite helper)
      • Recommendation: Use method forms (.regex(), .trim()) instead of .check() function

Future Support

(No items currently planned)

Determinism

When you pass the same seed to initGenerator, generated values are deterministic across runs.

  • Covered by seed determinism: strings, numbers, dates, arrays, unions, regex-based strings, cidrv6, base64url, etc.
  • Internally, we ensure that random sources (including RandExp for regex generation) are wired to the seeded Faker RNG.

Tips for custom generation via override

  • If you define a custom generator, use the provided options.faker rather than Math.random() or Date.now() to keep determinism.
import { type CustomGeneratorType, initGenerator } from 'zod-v4-mocks';
import { z } from 'zod';

const Email = z.email();

const deterministicOverride: CustomGeneratorType = (schema, options) => {
  const { faker } = options; // seeded
  if (schema === Email) {
    const user = faker.internet.userName();
    const host = faker.internet.domainName();
    return `${user}@${host}`;
  }
};

const gen = initGenerator({ seed: 12345 }).override(deterministicOverride);
const a = gen.generate(Email); // same output for the same seed
const b = gen.generate(Email); // same across runs with seed 12345

Note

In .templateLiteral

const schema = z.templateLiteral(['Value: ', z.undefined()]);
// or
const schema = z.templateLiteral(['Value: ', undefined]);
type Schema = z.infer<typeof schema>;
// typesciprt guess `type Schema = "Value: "`

But, zod expect "Value: undefined", so, this library would generate Value: undefined

Branded types

In Zod v4, brand() is a type-level concept. There is no special brand class exposed at runtime, so generated values follow the inner schema (e.g., z.string().brand<'UserId'>() generates a string). Meanwhile, thanks to the overload initGenerator().generate<T extends z.ZodTypeAny>(schema: T): z.infer<T>, the returned value type is inferred with the brand.

const BrandedUserId = z.string().brand<'UserId'>();
const val = initGenerator().generate(BrandedUserId);
// The type is inferred as branded
type T = z.infer<typeof BrandedUserId>;
const _: T = val; // OK

About

Mock generator for zod v4

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors