Factory Composition
Build complex objects by combining simpler factories.
The compose() Method
Merge multiple factory definitions while maintaining type safety:
interface User {
id: string;
name: string;
}
interface ContactInfo {
email: string;
phone: string;
}
interface FullUser extends User, ContactInfo {
isVerified: boolean;
}
const userFactory = new Factory<User>((faker) => ({
id: faker.string.uuid(),
name: faker.person.fullName(),
}));
const contactFactory = new Factory<ContactInfo>((faker) => ({
email: faker.internet.email(),
phone: faker.phone.number(),
}));
// Compose factories
const fullUserFactory = userFactory.compose<FullUser>({
email: contactFactory.build().email,
phone: contactFactory.build().phone,
isVerified: true,
});
Factory-Based Composition
Use entire factories as property values:
const enhancedUserFactory = userFactory.compose<EnhancedUser>({
profile: profileFactory, // Will call profileFactory.build()
address: addressFactory, // Will call addressFactory.build()
membershipLevel: 'gold',
});
Nested Composition
Build hierarchies by composing multiple times:
// Base → Employee → Manager → Executive
const personFactory = new Factory<Person>(personFn);
const employeeFactory = personFactory.compose<Employee>({
/* employee props */
});
const managerFactory = employeeFactory.compose<Manager>({
/* manager props */
});
const executiveFactory = managerFactory.compose<Executive>({
/* executive props */
});
Conditional Composition
Create different compositions based on conditions:
function createAccountFactory(type: 'basic' | 'premium' | 'enterprise') {
const baseComposition = { type, isActive: true };
switch (type) {
case 'basic':
return baseFactory.compose<BasicAccount>({
...baseComposition,
storageLimit: 1,
features: ['basic-support'],
});
case 'premium':
return baseFactory.compose<PremiumAccount>({
...baseComposition,
storageLimit: 100,
features: ['priority-support', 'analytics'],
});
case 'enterprise':
return baseFactory.compose<EnterpriseAccount>({
...baseComposition,
storageLimit: -1, // unlimited
dedicatedManager: userFactory,
});
}
}
Composition with Relationships
Build object graphs:
const teamFactory = userFactory.compose<Team>({
name: faker.company.name(),
manager: userFactory,
members: () => userFactory.batch(faker.number.int({ min: 3, max: 8 })),
project: projectFactory,
});
Async Composition
Works with async factories:
const userFactory = new Factory<User>(async (faker) => ({
id: faker.string.uuid(),
name: faker.person.fullName(),
}));
const composedFactory = userFactory.compose<UserWithProfile>({
profile: asyncProfileFactory,
isActive: true,
});
// Use buildAsync for async composition
const user = await composedFactory.buildAsync();
Best Practices
- Type Safety: Ensure composed types extend the base type
- Factory Reuse: Create factory instances once, reuse in compositions
- Performance: Avoid creating new factories in compositions
- Logical Grouping: Group related properties into composable factories
// ❌ Inefficient
const bad = userFactory.compose({
profile: new Factory(profileFn), // New instance each build
});
// ✅ Efficient
const profileFactory = new Factory(profileFn);
const good = userFactory.compose({
profile: profileFactory, // Reused instance
});