Interface-Forge is a TypeScript library for creating strongly typed mock data factories. Built on top of Faker.js, it provides a powerful Factory
class that combines Faker's data generation capabilities with TypeScript's type safety, advanced composition patterns, and optional Zod schema integration.
use()
method for lazy evaluationbuild({ ... })
methodbeforeBuild
and afterBuild
hooks to transform data, validate business rules, or fetch async dataπ Browse Example Code - See Interface-Forge in action with practical examples
Choose your preferred package manager:
# npm
npm install --save-dev interface-forge
# yarn
yarn add --dev interface-forge
# pnpm
pnpm add --save-dev interface-forge
For Zod integration (optional):
# npm
npm install zod
# yarn
yarn add zod
# pnpm
pnpm add zod
Note: ZodFactory supports both Zod v3 and v4.
To create a factory, you need a TypeScript type:
// types.ts
interface User {
firstName: string;
lastName: string;
email: string;
profile: {
profession: string;
gender: string;
age: number;
};
}
Pass the desired type as a generic argument when instantiating the Factory
class, alongside default values for the factory:
// factories.ts
import { Factory } from 'interface-forge';
import { User } from './types';
const UserFactory = new Factory<User>((factory, iteration) => ({
firstName: factory.person.firstName(),
lastName: factory.person.lastName(),
email: factory.internet.email(),
profile: {
profession: factory.person.jobType(),
gender: factory.person.gender(),
age: 27 + iteration,
},
}));
Then use the factory to create an object of the desired type in a test file:
// User.spec.ts
describe('User', () => {
const user = UserFactory.build();
// user == {
// firstName: "Johanne",
// lastName: "Smith",
// email: "js@example.com",
// profile: {
// profession: "Journalist",
// gender: "Female",
// age: 27
// },
// }
// ...
});
Interface-Forge supports generating and caching test data as fixtures, making it easy to create consistent test data across multiple test runs. Fixtures are generated using the generateFixture
option in the build()
and buildAsync()
methods.
const UserFactory = new Factory<User>(
(faker) => ({
id: faker.string.uuid(),
name: faker.person.fullName(),
email: faker.internet.email(),
}),
{
fixtures: { basePath: './test/fixtures' },
},
);
// Creates or loads fixture from ./test/fixtures/__fixtures__/user.json
const user = UserFactory.build(undefined, { generateFixture: 'user' });
// With overrides
const adminUser = UserFactory.build(
{ role: 'admin' },
{ generateFixture: 'admin-user' },
);
// Auto-generated fixture path (uses factory name and timestamp)
const autoUser = UserFactory.build(undefined, { generateFixture: true });
// Async fixtures
const asyncUser = await UserFactory.buildAsync(undefined, {
generateFixture: 'async-user',
});
// Configure at factory level for all builds
const FixturedFactory = new Factory<User>(
(faker) => ({
// ... factory definition
}),
{
fixtures: { basePath: './test/fixtures' },
generateFixture: 'default-user',
},
);
// All builds will use fixtures
const user1 = FixturedFactory.build();
const user2 = FixturedFactory.build(); // Returns cached data
Configure how fixtures are stored and validated:
const factory = new Factory<User>(
(faker) => ({
// ... factory definition
}),
{
fixtures: {
// Base directory for fixtures (default: process.cwd())
basePath: './test/fixtures',
// Subdirectory name (default: '__fixtures__')
directory: 'my-fixtures',
// Validate factory signature changes (default: true)
validateSignature: true,
// Include factory source in signature (default: true)
includeSource: true,
// Use subdirectories (default: true)
useSubdirectory: true,
},
},
);
Control where fixtures are stored:
// Default behavior: ./test/fixtures/__fixtures__/user.json
factory.build(undefined, { generateFixture: 'user' });
// Nested paths: ./test/fixtures/api/v1/__fixtures__/user.json
factory.build(undefined, { generateFixture: 'api/v1/user' });
// No subdirectory: ./test/fixtures/user.json
const factory = new Factory<User>(/* ... */, {
fixtures: {
basePath: './test/fixtures',
useSubdirectory: false
}
});
factory.build(undefined, { generateFixture: 'user' });
// Custom directory name: ./test/fixtures/cached/user.json
const factory = new Factory<User>(/* ... */, {
fixtures: {
basePath: './test/fixtures',
directory: 'cached'
}
});
factory.build(undefined, { generateFixture: 'user' });
Interface-Forge validates fixtures to ensure they match the current factory configuration:
// Factory signature includes:
// - Factory constructor name
// - Factory function source (if includeSource: true)
// - Relevant options (maxDepth)
// - Hook counts
// If factory changes, fixture validation will fail
const factory1 = new Factory<User>((faker) => ({
name: faker.person.firstName()
}));
factory1.build(undefined, { generateFixture: 'user' }); // Creates fixture
// Different factory = different signature
const factory2 = new Factory<User>((faker) => ({
name: faker.person.firstName(),
email: faker.internet.email() // Added field
}), {
fixtures: { basePath: './test/fixtures' }
});
factory2.build(undefined, { generateFixture: 'user' }); // Throws FixtureValidationError
// Disable validation if needed
const factory3 = new Factory<User>(/* ... */, {
fixtures: { validateSignature: false }
});
Fixtures are ideal for:
Fixtures work seamlessly with ZodFactory:
import { z } from 'zod/v4';
import { ZodFactory } from 'interface-forge/zod';
const UserSchema = z.object({
id: z.uuid(),
name: z.string(),
email: z.email(),
});
const factory = new ZodFactory(UserSchema, {
fixtures: { basePath: './test/fixtures' },
});
// Schema information is included in fixture signature
const user = factory.build(undefined, { generateFixture: 'zod-user' });
Interface-Forge allows you to extend the base Factory class to add custom functionality:
class CustomFactory<T> extends Factory<T> {
// Add custom methods
buildWithTimestamp(): T & { timestamp: Date } {
const instance = this.build();
return { ...instance, timestamp: new Date() };
}
// Override existing methods
build(kwargs?: Partial<T>): T {
console.log('Building instance...');
return super.build(kwargs);
}
}
const factory = new CustomFactory<User>((f) => ({
/* ... */
}));
const userWithTimestamp = factory.buildWithTimestamp();
build
Builds a single object based on the factory's schema. Optionally, you can pass an object to override specific properties and/or options to control fixture generation.
Signature:
build(kwargs?: Partial<T>, options?: { generateFixture?: boolean | string }): T
Usage:
const user = UserFactory.build();
// user == {
// firstName: "Johanne",
// lastName: "Smith",
// email: "js@example.com",
// profile: {
// profession: "Journalist",
// gender: "Female",
// age: 27
// },
// }
const customUser = UserFactory.build({
profile: { age: 35 },
});
// With fixture generation
const fixturedUser = UserFactory.build(undefined, {
generateFixture: 'test-user',
});
// customUser.profile.age == 35
batch
Generates a batch of objects based on the factory's schema. Optionally, you can pass an object or an array of objects to override specific properties for each instance.
Signature:
batch(size: number, kwargs?: Partial<T> | Partial<T>[]): T[]
Usage:
const users = UserFactory.batch(3);
// users == [
// { ... },
// { ... },
// { ... }
// ]
const customUsers = UserFactory.batch(3, {
profile: { age: 35 },
});
// customUsers == [
// { ..., profile: { ..., age: 35 } },
// { ..., profile: { ..., age: 35 } },
// { ..., profile: { ..., age: 35 } }
// ]
const variedUsers = UserFactory.batch(3, [
{ profile: { age: 30 } },
{ profile: { age: 25 } },
{ profile: { age: 40 } },
]);
// variedUsers == [
// { ..., profile: { ..., age: 30 } },
// { ..., profile: { ..., age: 25 } },
// { ..., profile: { ..., age: 40 } }
// ]
batchAsync
Creates multiple instances asynchronously, allowing use of async factory functions. This method supports both synchronous and asynchronous factory functions and hooks.
Signature:
batchAsync(size: number, kwargs?: Partial<T> | Partial<T>[]): Promise<T[]>
Usage:
const UserFactory = new Factory<User>(async (factory) => ({
id: factory.string.uuid(),
email: factory.internet.email(),
apiKey: await generateApiKey(), // async operation
}));
// Create 5 users asynchronously
const users = await UserFactory.batchAsync(5);
// With overrides
const admins = await UserFactory.batchAsync(3, { role: 'admin' });
// With individual overrides
const customUsers = await UserFactory.batchAsync(2, [
{ email: 'first@example.com' },
{ email: 'second@example.com' },
]);
use
Creates a reference to a function that can be used within the factory. This method allows for the encapsulation of a function and its arguments, enabling deferred execution.
Signature:
use<C extends (...args: never) => unknown>(handler: C, ...args: Parameters<C>): ReturnType<C>
Usage:
const complexFactory = new Factory<ComplexObject>((factory) => ({
name: factory.person.firstName(),
value: factory.number.int({ min: 1, max: 3 }),
options: {
type: '1',
},
}));
const factoryWithOptions = new Factory<ComplexObject>((factory) => ({
...defaults,
options: {
type: '1',
children: factory.use(complexFactory.batch, 2),
},
}));
const result = factoryWithOptions.build();
// result.options.children == [
// { ... },
// { ... }
// ]
iterate
Cycles through the values of an iterable indefinitely.
Signature:
iterate<T>(iterable: Iterable<T>): Generator<T, T, T>
Usage:
const values = ['Value 1', 'Value 2', 'Value 3'];
const generator = UserFactory.iterate(values);
console.log(generator.next().value); // 'Value 1'
console.log(generator.next().value); // 'Value 2'
console.log(generator.next().value); // 'Value 3'
console.log(generator.next().value); // 'Value 1'
sample
Samples values randomly from an iterable, ensuring no immediate repetitions.
Signature:
sample<T>(iterable: Iterable<T>): Generator<T, T, T>
Usage:
const values = [1, 2, 3];
const generator = UserFactory.sample(values);
console.log(generator.next().value); // 1 (or 2, or 3)
console.log(generator.next().value); // (different from the previous value)
extend
Extends the current factory to create a new factory with additional or overridden properties. This method allows for factory inheritance, a new factory can be built upon an existing one while adding or modifying properties.
Signature:
extend<U extends T>(factoryFn: FactoryFunction<U>): Factory<U>
Usage:
// Base factory
const BaseUserFactory = new Factory<BaseUser>((factory) => ({
id: factory.string.uuid(),
createdAt: factory.date.recent(),
}));
// Extended factory
const AdminUserFactory = BaseUserFactory.extend<AdminUser>((factory) => ({
role: 'admin',
permissions: ['read', 'write', 'delete'],
}));
const admin = AdminUserFactory.build();
// admin == {
// id: "550e8400-e29b-41d4-a716-446655440000",
// createdAt: Date,
// role: "admin",
// permissions: ["read", "write", "delete"]
// }
compose
Composes the current factory with other factories to create a new factory. This method allows for factory composition, a factory can include properties generated by other factories or static values.
Signature:
compose<U extends T>(composition: FactoryComposition<U>): Factory<U>
Usage:
// User factory
const UserFactory = new Factory<User>((factory) => ({
name: factory.person.fullName(),
email: factory.internet.email(),
}));
// Post factory
const PostFactory = new Factory<Post>((factory) => ({
title: factory.helpers.arrayElement([
'Welcome to My Website',
'About Me',
'Contact Information',
]),
content: factory.helpers.arrayElement([
'Thanks for visiting my personal website.',
'I am a software developer passionate about coding.',
'Feel free to reach out through the contact form.',
]),
}));
// Composed factory
const UserWithPostsFactory = UserFactory.compose<UserWithPosts>({
posts: PostFactory.batch(3),
});
const userWithPosts = UserWithPostsFactory.build();
// userWithPosts == {
// name: "Johanne Smith",
// email: "js@example.com",
// posts: [
// { title: "First Post", content: "Content..." },
// { title: "Second Post", content: "Content..." },
// { title: "Third Post", content: "Content..." }
// ]
// }
// Mixing with static values
interface UserWithStatus extends User {
status: string;
}
const UserWithStatusFactory = UserFactory.compose<UserWithStatus>({
status: 'active',
});
const user = UserWithStatusFactory.build();
// user == {
// name: "Johanne Smith",
// email: "js@example.com",
// status: "active"
// }
partial
Creates a new factory where all properties are optional (Partial
Signature:
partial(): Factory<Partial<T>>
Usage:
// Original factory with required fields
const UserFactory = new Factory<User>((factory) => ({
id: factory.string.uuid(),
name: factory.person.fullName(),
email: factory.internet.email(),
age: factory.number.int({ min: 18, max: 80 }),
isActive: factory.datatype.boolean(),
}));
// Create a partial factory where all fields are optional
const PartialUserFactory = UserFactory.partial();
// Generate partial users with all fields populated
const partialUser = PartialUserFactory.build();
// partialUser: Partial<User> - all properties are generated but optional
// Override only specific fields
const updateData = PartialUserFactory.build({
email: 'newemail@example.com',
age: 30,
});
// updateData == {
// id: "generated-uuid",
// name: "generated-name",
// email: "newemail@example.com",
// age: 30,
// isActive: true
// }
// Useful for testing partial updates
const patchData = PartialUserFactory.build({
email: 'updated@example.com',
});
// Can be used for PATCH requests where only email is being updated
beforeBuild
Adds a hook that will be executed before building the instance. Hooks receive the partial parameters (kwargs) and can modify them before the instance is built.
Signature:
beforeBuild(hook: BeforeBuildHook<T>): this
Usage:
const UserFactory = new Factory<User>((factory) => ({
id: factory.string.uuid(),
email: '',
username: '',
})).beforeBuild((params) => {
// Auto-generate email from username if not provided
if (!params.email && params.username) {
params.email = `${params.username}@example.com`;
}
return params;
});
const user = UserFactory.build({ username: 'john_doe' });
// user.email == "john_doe@example.com"
afterBuild
Adds a hook that will be executed after building the instance. Hooks are executed in the order they were added and can be either synchronous or asynchronous.
Signature:
afterBuild(hook: AfterBuildHook<T>): this
Usage:
const ProductFactory = new Factory<Product>((factory) => ({
id: factory.string.uuid(),
name: factory.commerce.productName(),
price: factory.number.float({ min: 10, max: 1000 }),
formattedPrice: '',
})).afterBuild((product) => {
// Format price with currency
product.formattedPrice = `$${product.price.toFixed(2)}`;
return product;
});
const product = ProductFactory.build();
// product.formattedPrice == "$123.45"
buildAsync
Builds an instance asynchronously with all registered hooks applied in sequence. This method supports both synchronous and asynchronous hooks and fixture generation.
Signature:
buildAsync(kwargs?: Partial<T>, options?: { generateFixture?: boolean | string }): Promise<T>
Usage:
const UserFactory = new Factory<User>((factory) => ({
id: factory.string.uuid(),
email: factory.internet.email(),
isVerified: false,
})).afterBuild(async (user) => {
// Simulate API call to verify email
await verifyEmail(user.email);
user.isVerified = true;
return user;
});
// Must use buildAsync for async hooks
const user = await UserFactory.buildAsync();
// user.isVerified == true
// With fixture generation
const fixturedUser = await UserFactory.buildAsync(undefined, {
generateFixture: 'verified-user',
});
Important Hook Behavior:
build()
build()
will throw a ConfigurationError
buildAsync()
when you have async hooks or want consistent async behaviorcreate
Creates and persists a single instance to a database using a persistence adapter.
Signature:
create(kwargs?: Partial<T>, options?: CreateOptions<T>): Promise<T>
Usage:
import { MongooseAdapter } from 'interface-forge/examples/adapters/mongoose-adapter';
const UserFactory = new Factory<User>((factory) => ({
id: factory.string.uuid(),
email: factory.internet.email(),
name: factory.person.fullName(),
}));
// Option 1: Set default adapter
const factoryWithDb = UserFactory.withAdapter(new MongooseAdapter(UserModel));
const user = await factoryWithDb.create({ name: 'John' });
// Option 2: Pass adapter in options
const user2 = await UserFactory.create(
{ name: 'Jane' },
{ adapter: new MongooseAdapter(UserModel) },
);
createMany
Creates and persists multiple instances to a database in a batch operation.
Signature:
createMany(
size: number,
kwargs?: Partial<T> | Partial<T>[],
options?: CreateManyOptions<T>
): Promise<T[]>
Usage:
// Create 5 users with default adapter
const users = await factoryWithDb.createMany(5);
// With individual overrides
const users2 = await factoryWithDb.createMany(3, [
{ role: 'admin' },
{ role: 'user' },
{ role: 'guest' },
]);
// With adapter in options
const users3 = await UserFactory.createMany(10, undefined, {
adapter: prismaAdapter,
});
withAdapter
Sets the default persistence adapter for the factory instance.
Signature:
withAdapter(adapter: PersistenceAdapter<T>): this
Usage:
import { PrismaAdapter } from 'interface-forge/examples/adapters/prisma-adapter';
import { TypeORMAdapter } from 'interface-forge/examples/adapters/typeorm-adapter';
// Set default adapter for all operations
const userFactory = new Factory<User>(/* ... */).withAdapter(
new PrismaAdapter(prisma.user),
);
// Now all create/createMany calls will use this adapter
const user = await userFactory.create();
const users = await userFactory.createMany(5);
Interface-forge supports database persistence through adapters. Example adapters are provided for popular ORMs:
To implement a custom adapter, implement the PersistenceAdapter
interface:
interface PersistenceAdapter<T, R = T> {
create(data: T): Promise<R>;
createMany(data: T[]): Promise<R[]>;
}
Example custom adapter:
class CustomAdapter<T> implements PersistenceAdapter<T> {
async create(data: T): Promise<T> {
// Your persistence logic
return savedData;
}
async createMany(data: T[]): Promise<T[]> {
// Your batch persistence logic
return savedDataArray;
}
}
seed
Sets the seed for the random number generator to create deterministic data:
Signature:
seed(seed: number): this
Usage:
const factory = new Factory<User>(/* ... */);
factory.seed(12345);
// Will always generate the same data for the same seed
const user1 = factory.build();
const user2 = factory.build();
Interface-Forge is designed to work with TypeScript 5.x and above. It leverages TypeScript's type system to provide type safety and autocompletion for your factories.
Interface-Forge extends the Faker class from Faker.js, giving you access to all Faker.js functionalities directly from your factory instance. This means you can use any Faker.js method to generate data for your factory.
Example:
const UserFactory = new Factory<User>((factory) => ({
// Using Faker.js methods directly
firstName: factory.person.firstName(),
lastName: factory.person.lastName(),
email: factory.internet.email(),
avatar: factory.image.avatar(),
bio: factory.lorem.paragraph(),
// ... other properties
}));
For more information about available Faker.js methods, see the Faker.js documentation.
Interface-Forge provides seamless integration with Zod schemas through the ZodFactory
class. This allows you to generate mock data that automatically conforms to your Zod schema definitions.
Zod Version Support: ZodFactory supports both Zod v3 and Zod v4 schemas automatically. The factory detects the schema version and handles the differences transparently:
import { z } from 'zod'
import { z } from 'zod/v4'
Both versions work with the same ZodFactory API, so you can use existing v3 schemas without modification while also supporting newer v4 features.
// Works with both Zod v3 and v4
import { z } from 'zod'; // For Zod v3
// OR
import { z } from 'zod/v4'; // For Zod v4
import { ZodFactory } from 'interface-forge/zod';
const UserSchema = z.object({
id: z.string().uuid(),
name: z.string().min(1).max(100),
email: z.string().email(),
age: z.number().int().min(18).max(120),
isActive: z.boolean(),
role: z.enum(['admin', 'user', 'guest']),
});
const userFactory = new ZodFactory(UserSchema);
const user = userFactory.build();
// Automatically generates data that conforms to the schema
The ZodFactory
supports partial factory functions, allowing you to customize only specific fields while automatically generating the rest from the schema:
import { z } from 'zod'; // Works with both v3 and v4
import { ZodFactory } from 'interface-forge/zod';
const UserSchema = z.object({
id: z.uuid(),
name: z.string(),
email: z.email(),
role: z.enum(['admin', 'user', 'guest']),
createdAt: z.date(),
settings: z.object({
theme: z.enum(['light', 'dark']),
notifications: z.boolean(),
}),
});
// Partial factory function - only customize what you need
const userFactory = new ZodFactory(UserSchema, (factory) => ({
// Only customize these fields for deterministic test data
role: 'user', // Always create regular users
name: factory.person.fullName(),
settings: {
theme: 'light', // Always use light theme in tests
// notifications will be auto-generated from schema
},
// id, email, createdAt will be auto-generated from schema
}));
const user = userFactory.build();
// user.role === 'user' (from factory)
// user.name === 'John Doe' (from factory)
// user.settings.theme === 'light' (from factory)
// user.settings.notifications === true/false (auto-generated)
// user.id === uuid (auto-generated)
// user.email === valid email (auto-generated)
// user.createdAt === Date (auto-generated)
ZodFactory supports all major Zod types including:
Register custom generators for specific field types:
const factory = new ZodFactory(UserSchema, {
generators: {
userId: () => `USR_${Date.now()}`,
customEmail: (faker) =>
`test_${faker.string.alphanumeric(5)}@example.com`,
},
});
For advanced customization, register custom handlers for specific Zod types:
import { z } from 'zod';
import { ZodFactory } from 'interface-forge/zod';
const factory = new ZodFactory(UserSchema)
// Single type handler
.withTypeHandler('ZodFunction', () => () => 'mock function')
// Multiple type handlers
.withTypeHandlers({
ZodPromise: (schema, generator) => Promise.resolve('mock result'),
ZodBigInt: () => BigInt(42),
ZodCustomType: (schema, generator, depth) => 'custom value',
});
Required for Functions and Promises: Zod z.function()
and z.promise()
schemas require custom type handlers as they cannot be automatically generated:
const SchemaWithFunction = z.object({
callback: z.function(),
asyncResult: z.promise(z.string()),
});
const factory = new ZodFactory(SchemaWithFunction)
.withTypeHandler('ZodFunction', () => jest.fn())
.withTypeHandler('ZodPromise', () => Promise.resolve('test result'));
All generated data is guaranteed to be valid according to your Zod schema, including:
When using partial factory functions, TypeScript may show errors because it doesn't recognize that ZodFactory will auto-generate missing required fields. This is a known limitation. Your code will work correctly at runtime, but you may need to use type assertions in some cases:
const factory = new ZodFactory(
Schema,
(faker) =>
({
// Only some fields defined here
}) as any,
); // Type assertion if needed
Factory Examples:
ZodFactory Examples:
When generating large batches, consider:
Optimization Strategies:
// β Inefficient: Large single batch
const users = UserFactory.batch(10000); // High memory usage
// β
Better: Chunked generation
function* generateUsers(total: number, chunkSize: number = 1000) {
for (let i = 0; i < total; i += chunkSize) {
const remaining = Math.min(chunkSize, total - i);
yield UserFactory.batch(remaining);
}
}
// β
Best: Stream processing
async function processUsers(count: number) {
for (const chunk of generateUsers(count)) {
await processChunk(chunk);
// Memory is freed between chunks
}
}
Factory Reuse: Create factories once and reuse them:
// β Creates new factory each time
function createUser() {
return new Factory<User>(() => ({
/* schema */
})).build();
}
// β
Reuse factory instance
const UserFactory = new Factory<User>(() => ({
/* schema */
}));
function createUser() {
return UserFactory.build();
}
Deterministic Data: Use seed()
for consistent memory patterns:
// Consistent memory usage across test runs
const factory = UserFactory.seed(12345);
const users = factory.batch(1000);
For recursive or circular data structures:
maxDepth
option to limit recursion (especially with Zod)use()
for lazy evaluation to avoid infinite loopsWhen using maxDepth
with ZodFactory, be aware that the factory returns empty objects {}
when the depth limit is reached. This can cause Zod validation to fail if your schema has required nested fields at the depth limit:
// β Problematic: Required nested objects will fail validation
const ProblematicSchema = z.object({
level1: z.object({
level2: z.object({
level3: z.object({
level4: z.object({
value: z.string(), // Required field
}),
}),
}),
}),
});
const factory = new ZodFactory(ProblematicSchema, { maxDepth: 3 });
// This will throw a ZodError because level4 will be an empty object
// missing the required 'value' field
// β
Better: Use optional for deeply nested objects
const CompatibleSchema = z.object({
level1: z.object({
level2: z.object({
level3: z.object({
level4: z
.object({
value: z.string(),
})
.optional(), // Make deep nesting optional
}),
}),
}),
});
const factory = new ZodFactory(CompatibleSchema, { maxDepth: 3 });
const result = factory.build(); // Works correctly
// β
Best: Design recursive schemas with optional children
const RecursiveSchema = z.lazy(() =>
z.object({
name: z.string(),
children: z.array(RecursiveSchema).optional(), // Self-referencing with optional
}),
);
const recursiveFactory = new ZodFactory(RecursiveSchema, { maxDepth: 3 });
Interface-Forge throws specific error types for different scenarios:
import { ConfigurationError } from 'interface-forge';
try {
// β This will throw ConfigurationError
const factory = new Factory<User>((f) => ({
name: f.person.fullName(),
asyncData: Promise.resolve('data'), // Async data in sync factory
}));
factory.build(); // Throws: Cannot use build() with async factory functions
} catch (error) {
if (error instanceof ConfigurationError) {
console.log('Configuration issue:', error.message);
// Use buildAsync() instead
const result = await factory.buildAsync();
}
}
Schema Validation Failures:
const StrictSchema = z.object({
email: z.string().email(),
age: z.number().min(18),
});
const factory = new ZodFactory(StrictSchema);
try {
const user = factory.build({
email: 'invalid-email', // Will cause validation error
});
} catch (error) {
console.log('Validation failed:', error.message);
// Handle validation errors gracefully
}
Missing Type Handlers:
const FunctionSchema = z.object({
callback: z.function(), // Requires custom handler
});
try {
const factory = new ZodFactory(FunctionSchema);
factory.build(); // Throws error
} catch (error) {
console.log('Add type handler:', error.message);
// Fix by adding handler
const fixedFactory = factory.withTypeHandler('ZodFunction', () =>
jest.fn(),
);
const result = fixedFactory.build(); // Works
}
const factory = new Factory<User>((f) => ({
/* schema */
}))
.beforeBuild((params) => {
if (!params.email) {
throw new Error('Email is required');
}
return params;
})
.afterBuild((user) => {
// Validate business rules
if (user.age < 0) {
throw new Error('Invalid age');
}
return user;
});
try {
const user = factory.build({ age: -5 });
} catch (error) {
console.log('Hook validation failed:', error.message);
// Handle or retry with valid data
const validUser = factory.build({ age: 25, email: 'test@example.com' });
}
const asyncFactory = new Factory<User>((f) => ({
/* schema */
})).afterBuild(async (user) => {
try {
// Simulate external API call
const enrichedData = await fetchUserData(user.id);
return { ...user, ...enrichedData };
} catch (apiError) {
// Graceful fallback
console.warn('API enrichment failed, using defaults');
return { ...user, enrichmentFailed: true };
}
});
try {
const user = await asyncFactory.buildAsync();
} catch (error) {
console.log('Factory error:', error.message);
}
// Base factory with common fields
const BaseEntityFactory = new Factory<BaseEntity>((f) => ({
id: f.string.uuid(),
createdAt: f.date.past(),
updatedAt: f.date.recent(),
}));
// Extend for specific entities
const UserFactory = BaseEntityFactory.extend<User>((f) => ({
email: f.internet.email(),
name: f.person.fullName(),
}));
let userCounter = 0;
const SequentialUserFactory = new Factory<User>((f) => ({
id: ++userCounter,
email: `user${userCounter}@example.com`,
// ... other fields
}));
const UserFactory = new Factory<User>((f) => {
const isPremium = f.datatype.boolean();
return {
subscription: isPremium ? 'premium' : 'free',
features: isPremium
? ['feature1', 'feature2', 'feature3']
: ['feature1'],
// ... other fields
};
});
We welcome contributions from the community! Please read our contributing guidelines for more information.
This project is licensed under the MIT License. See the LICENSE file for more details.