zod-v4-mocks
v1.0.15
Published
Mock generator for zod v4
Downloads
8,177
Maintainers
Readme
zod-v4-mocks
Mock generator for zod v4
Installation
npm install zod-v4-mocksBasic 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.ZodLazyis partially supported- If schema has toplevel
z.union, this library would throw error - If schema has
z.tupleorz.intersectionanywhere, this library would cause RangeError
- If schema has toplevel
z.ZodIntersectionis 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
- Same schema types are basically supported
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 supportedz.string().check(z.minLength(10))- ❌ NOT supportedz.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
RandExpfor 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.fakerrather thanMath.random()orDate.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 12345Using path and key in override for field-specific control
The options argument passed to your custom generator includes path and key, allowing you to override values based on field location:
options.path: Array of keys/indexes from root to current field (e.g.,['user', 'id']or['items', 0, 'label'])options.key: The immediate key or index for the current field (e.g.,'id'or0)
Naming rules for path and key
| Schema Type | key | path | Example |
| -------------------- | ----------------------------- | ------------------------------- | ---------------------------------------------------------------------------------------- |
| Object property | Property name (string) | [...parentPath, key] | z.object({ id: z.string() }) → key: 'id', path: ['id'] |
| Array element | Index (number) | [...parentPath, index] | z.array(z.string()) → key: 0, path: [0] for first element |
| Tuple element | Index (number) | [...parentPath, index] | z.tuple([z.string(), z.number()]) → key: 0, path: [0] for first element |
| Record entry | Generated key (string/number) | [...parentPath, generatedKey] | z.record(z.string(), z.number()) → key: 'someKey', path: ['someKey'] |
| Set element | Index (number) | [...parentPath, index] | z.set(z.string()) → key: 0, path: [0] for first element |
| Map value | '(map)' (string literal) | [...parentPath, '(map)'] | z.map(z.string(), z.number()) → key: '(map)', path: ['(map)'] for values |
| Nested structure | Varies by type | Accumulated from root | z.object({ user: z.object({ id: z.string() }) }) → key: 'id', path: ['user', 'id'] |
Notes:
pathis always an array accumulated from the root to the current field- For arrays/tuples/sets,
keyis the numeric index (0, 1, 2, ...) - For objects,
keyis the property name as a string - For records,
keyis the generated key (string or number depending on keyType) - For maps, we use the special literal
'(map)'as bothkeyand path segment for values (since map keys can be any type) - Root-level fields have
pathwith a single element:['fieldName']or[0]
import { z } from 'zod';
import { initGenerator, type CustomGeneratorType } from 'zod-v4-mocks';
const schema = z.object({
id: z.uuidv4(),
user: z.object({
id: z.uuidv4(),
name: z.string(),
}),
items: z.array(
z.object({
id: z.uuidv4(),
label: z.string(),
}),
),
});
const overrideByPath: CustomGeneratorType = (schema, options) => {
const { path, key, faker } = options;
const pathStr = path.join('.');
// Override root-level id
if (pathStr === 'id') return 'root-fixed-id';
// Override user.id specifically
if (pathStr === 'user.id') return 'user-fixed-id';
// Override all id fields inside items array
if (pathStr.startsWith('items.') && key === 'id') {
return faker.string.uuid(); // seeded, deterministic
}
// Fallback to default generator
return undefined;
};
const gen = initGenerator({ seed: 123 }).override(overrideByPath);
const result = gen.generate(schema);
// result.id === 'root-fixed-id'
// result.user.id === 'user-fixed-id'
// result.items[0].id and result.items[1].id are seeded UUIDsNote
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