/** * Test Data Factory Template * * This template demonstrates best practices for creating reusable * test data factories using the Factory pattern. * * Benefits: * - Consistent test data generation * - Easy to customize with overrides * - Reduces test setup boilerplate * - Type-safe with TypeScript */ import { faker } from '@faker-js/faker'; // ============================================================================ // TYPES // ============================================================================ export interface User { id: string; email: string; username: string; firstName: string; lastName: string; role: 'admin' | 'user' | 'guest'; isActive: boolean; createdAt: Date; updatedAt: Date; profile?: UserProfile; } export interface UserProfile { bio: string; avatar: string; phoneNumber: string; address: Address; } export interface Address { street: string; city: string; state: string; zipCode: string; country: string; } export interface Product { id: string; name: string; description: string; price: number; category: string; inStock: boolean; quantity: number; imageUrl: string; createdAt: Date; } export interface Order { id: string; userId: string; items: OrderItem[]; subtotal: number; tax: number; total: number; status: 'pending' | 'processing' | 'shipped' | 'delivered' | 'cancelled'; shippingAddress: Address; createdAt: Date; updatedAt: Date; } export interface OrderItem { productId: string; quantity: number; price: number; } // ============================================================================ // FACTORY FUNCTIONS // ============================================================================ /** * User Factory * * Creates realistic user test data with sensible defaults */ export class UserFactory { /** * Create a single user * * @param overrides - Partial user object to override defaults * @returns Complete user object * * @example * ```ts * const admin = UserFactory.create({ role: 'admin' }); * const inactiveUser = UserFactory.create({ isActive: false }); * ``` */ static create(overrides: Partial = {}): User { const firstName = faker.person.firstName(); const lastName = faker.person.lastName(); const email = overrides.email || faker.internet.email({ firstName, lastName }); return { id: faker.string.uuid(), email, username: faker.internet.userName({ firstName, lastName }), firstName, lastName, role: 'user', isActive: true, createdAt: faker.date.past(), updatedAt: faker.date.recent(), ...overrides, }; } /** * Create multiple users * * @param count - Number of users to create * @param overrides - Partial user object to override defaults for all users * @returns Array of user objects * * @example * ```ts * const users = UserFactory.createMany(5); * const admins = UserFactory.createMany(3, { role: 'admin' }); * ``` */ static createMany(count: number, overrides: Partial = {}): User[] { return Array.from({ length: count }, () => this.create(overrides)); } /** * Create admin user * * @param overrides - Partial user object to override defaults * @returns Admin user object */ static createAdmin(overrides: Partial = {}): User { return this.create({ role: 'admin', ...overrides, }); } /** * Create user with complete profile * * @param overrides - Partial user object to override defaults * @returns User with profile object */ static createWithProfile(overrides: Partial = {}): User { return this.create({ profile: { bio: faker.person.bio(), avatar: faker.image.avatar(), phoneNumber: faker.phone.number(), address: AddressFactory.create(), }, ...overrides, }); } /** * Create inactive user * * @param overrides - Partial user object to override defaults * @returns Inactive user object */ static createInactive(overrides: Partial = {}): User { return this.create({ isActive: false, ...overrides, }); } } /** * Address Factory * * Creates realistic address test data */ export class AddressFactory { static create(overrides: Partial
= {}): Address { return { street: faker.location.streetAddress(), city: faker.location.city(), state: faker.location.state(), zipCode: faker.location.zipCode(), country: faker.location.country(), ...overrides, }; } static createUS(overrides: Partial
= {}): Address { return this.create({ country: 'United States', zipCode: faker.location.zipCode('#####'), state: faker.location.state({ abbreviated: true }), ...overrides, }); } } /** * Product Factory * * Creates realistic product test data */ export class ProductFactory { static create(overrides: Partial = {}): Product { return { id: faker.string.uuid(), name: faker.commerce.productName(), description: faker.commerce.productDescription(), price: parseFloat(faker.commerce.price()), category: faker.commerce.department(), inStock: true, quantity: faker.number.int({ min: 0, max: 100 }), imageUrl: faker.image.url(), createdAt: faker.date.past(), ...overrides, }; } static createMany(count: number, overrides: Partial = {}): Product[] { return Array.from({ length: count }, () => this.create(overrides)); } static createOutOfStock(overrides: Partial = {}): Product { return this.create({ inStock: false, quantity: 0, ...overrides, }); } static createExpensive(overrides: Partial = {}): Product { return this.create({ price: faker.number.int({ min: 1000, max: 10000 }), ...overrides, }); } } /** * Order Factory * * Creates realistic order test data with items */ export class OrderFactory { static create(overrides: Partial = {}): Order { const items = overrides.items || [ { productId: faker.string.uuid(), quantity: faker.number.int({ min: 1, max: 5 }), price: parseFloat(faker.commerce.price()), }, ]; const subtotal = items.reduce((sum, item) => sum + item.price * item.quantity, 0); const tax = subtotal * 0.08; // 8% tax const total = subtotal + tax; return { id: faker.string.uuid(), userId: faker.string.uuid(), items, subtotal, tax, total, status: 'pending', shippingAddress: AddressFactory.createUS(), createdAt: faker.date.recent(), updatedAt: faker.date.recent(), ...overrides, }; } static createMany(count: number, overrides: Partial = {}): Order[] { return Array.from({ length: count }, () => this.create(overrides)); } static createWithItems(items: OrderItem[], overrides: Partial = {}): Order { return this.create({ items, ...overrides }); } static createShipped(overrides: Partial = {}): Order { return this.create({ status: 'shipped', ...overrides, }); } static createCancelled(overrides: Partial = {}): Order { return this.create({ status: 'cancelled', ...overrides, }); } } // ============================================================================ // BUILDER PATTERN (Advanced) // ============================================================================ /** * User Builder * * Provides a fluent interface for building complex user objects * * @example * ```ts * const user = new UserBuilder() * .withEmail('admin@example.com') * .withRole('admin') * .withProfile() * .build(); * ``` */ export class UserBuilder { private user: Partial = {}; withId(id: string): this { this.user.id = id; return this; } withEmail(email: string): this { this.user.email = email; return this; } withUsername(username: string): this { this.user.username = username; return this; } withName(firstName: string, lastName: string): this { this.user.firstName = firstName; this.user.lastName = lastName; return this; } withRole(role: User['role']): this { this.user.role = role; return this; } withProfile(profile?: UserProfile): this { this.user.profile = profile || { bio: faker.person.bio(), avatar: faker.image.avatar(), phoneNumber: faker.phone.number(), address: AddressFactory.create(), }; return this; } inactive(): this { this.user.isActive = false; return this; } active(): this { this.user.isActive = true; return this; } build(): User { return UserFactory.create(this.user); } } // ============================================================================ // USAGE EXAMPLES // ============================================================================ /** * Example: Simple user creation */ export function exampleSimpleUser() { const user = UserFactory.create(); const admin = UserFactory.createAdmin(); const users = UserFactory.createMany(5); return { user, admin, users }; } /** * Example: Customized user creation */ export function exampleCustomUser() { const user = UserFactory.create({ email: 'custom@example.com', role: 'admin', isActive: false, }); return user; } /** * Example: Builder pattern */ export function exampleBuilder() { const user = new UserBuilder() .withEmail('builder@example.com') .withName('John', 'Doe') .withRole('admin') .withProfile() .active() .build(); return user; } /** * Example: Order with products */ export function exampleOrder() { // Create products const products = ProductFactory.createMany(3); // Create order items from products const items: OrderItem[] = products.map((product) => ({ productId: product.id, quantity: faker.number.int({ min: 1, max: 3 }), price: product.price, })); // Create order with items const order = OrderFactory.createWithItems(items); return { products, order }; } // ============================================================================ // TEST USAGE // ============================================================================ /** * Example test using factories */ import { describe, it, expect } from 'vitest'; describe('UserService (using factories)', () => { it('should create user', () => { // ARRANGE const userData = UserFactory.create(); // ACT const result = userService.create(userData); // ASSERT expect(result.id).toBeDefined(); expect(result.email).toBe(userData.email); }); it('should only allow admins to delete users', () => { // ARRANGE const admin = UserFactory.createAdmin(); const regularUser = UserFactory.create(); // ACT & ASSERT expect(() => userService.deleteUser(regularUser.id, admin)).not.toThrow(); expect(() => userService.deleteUser(admin.id, regularUser)).toThrow('Unauthorized'); }); it('should calculate order total correctly', () => { // ARRANGE const order = OrderFactory.create({ items: [ { productId: '1', quantity: 2, price: 50 }, { productId: '2', quantity: 1, price: 30 }, ], }); // ACT const total = orderService.calculateTotal(order); // ASSERT expect(total).toBe(140.4); // (50*2 + 30*1) * 1.08 tax }); }); // ============================================================================ // BEST PRACTICES // ============================================================================ /* ✅ Use realistic data (faker.js) ✅ Provide sensible defaults ✅ Allow overrides for customization ✅ Type-safe with TypeScript ✅ Create helper methods (createAdmin, createInactive, etc.) ✅ Builder pattern for complex objects ✅ Consistent naming (create, createMany, createWith...) ✅ Document with JSDoc ✅ Export all factories for reuse ✅ Keep factories simple and focused */