UNPKG

ts-forged

Version:

Type-safe fake data generation from DTOs for TypeScript and NestJS

818 lines (665 loc) 21.3 kB
[![npm version](https://badge.fury.io/js/ts-forged.svg)](https://badge.fury.io/js/ts-forged) [![npm downloads](https://img.shields.io/npm/dm/ts-forged.svg)](https://npmjs.com/package/ts-forged) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![TypeScript](https://img.shields.io/badge/TypeScript-5.3+-blue.svg)](https://www.typescriptlang.org/) [![Node.js](https://img.shields.io/badge/Node.js-16+-green.svg)](https://nodejs.org/) [![Build Status](https://github.com/Tyler-Churchill/ts-forged/workflows/Build/badge.svg)](https://github.com/Tyler-Churchill/ts-forged/actions) [![Test Coverage](https://img.shields.io/badge/coverage-95%25-brightgreen.svg)](https://github.com/Tyler-Churchill/ts-forged) # ts-forged A TypeScript library for generating type-safe fake data from DTOs, perfect for testing and development. Supports both NestJS decorators (`@ApiProperty`, `class-validator`) and plain TypeScript classes. ## 📋 Table of Contents - [Features](#-features) - [Installation](#-installation) - [Quick Start](#-quick-start) - [Usage Examples](#-usage-examples) - [API Reference](#-api-reference) - [Advanced Usage](#-advanced-usage) - [Contributing](#-contributing) - [License](#-license) ## ✨ Features - 🎯 **Type-safe fake data generation** - Full TypeScript support with compile-time type checking - 🔍 **Smart field name detection** - Automatically generates appropriate data based on field names - 🎨 **Customizable field overrides** - Override specific fields while generating the rest - 🏗️ **Factory pattern** - Clean, reusable test fixtures - 🔄 **NestJS integration** - Works seamlessly with `@ApiProperty` and `class-validator` decorators - 📦 **Zero runtime dependencies** - Lightweight with minimal footprint - 🎲 **Enum and union support** - Handles complex TypeScript types - 📊 **Multiple generation methods** - `build()`, `buildMany()`, and `sequence()` - 🚀 **Performance optimized** - Fast generation with tree-shaking support ## 📦 Installation ```bash npm install ts-forged # or yarn add ts-forged # or pnpm add ts-forged ``` ## 🚀 Quick Start ### Basic Usage ```typescript import { Factory } from 'ts-forged'; // Define your DTO class UserDto { firstName!: string; lastName!: string; email!: string; age!: number; } // Create a factory const userFactory = Factory.create(UserDto); // Generate fake data const fakeUser = userFactory.build(); console.log(fakeUser); // Output: // { // firstName: 'John', // lastName: 'Doe', // email: 'john.doe@example.com', // age: 25 // } // Generate with overrides const specificUser = userFactory.build({ firstName: 'John', age: 30, }); console.log(specificUser); // Output: // { // firstName: 'John', // Overridden // lastName: 'Smith', // Generated // email: 'john.smith@example.com', // Generated // age: 30 // Overridden // } ``` ## 📚 Usage Examples ### 1. NestJS with @ApiProperty and class-validator ```typescript import { Factory } from 'ts-forged'; import { ApiProperty } from '@nestjs/swagger'; import { IsEmail, IsDate, IsOptional, Min, Max, IsEnum } from 'class-validator'; enum UserRole { ADMIN = 'admin', USER = 'user', MODERATOR = 'moderator', } class AddressDto { @ApiProperty({ description: 'Street address' }) street!: string; @ApiProperty({ description: 'City name' }) city!: string; @ApiProperty({ description: 'Postal code' }) postalCode!: string; } class UserDto { @ApiProperty({ description: 'User first name' }) firstName!: string; @ApiProperty({ description: 'User last name' }) lastName!: string; @ApiProperty({ description: 'User email address' }) @IsEmail() email!: string; @ApiProperty({ description: 'User age', minimum: 18, maximum: 80 }) @Min(18) @Max(80) age!: number; @ApiProperty({ description: 'User birth date' }) @IsDate() birthDate!: Date; @ApiProperty({ description: 'Whether user is active' }) isActive!: boolean; @ApiProperty({ enum: UserRole, description: 'User role' }) @IsEnum(UserRole) role!: UserRole; @ApiProperty({ type: [AddressDto], required: false }) @IsOptional() addresses?: AddressDto[]; } // Create factory const userFactory = Factory.create(UserDto); // Generate single user const user = userFactory.build(); console.log(user); // Output: // { // firstName: 'Emma', // lastName: 'Johnson', // email: 'emma.johnson@example.com', // age: 28, // birthDate: 2024-01-15T10:30:00.000Z, // isActive: true, // role: 'user', // addresses: [ // { // street: '123 Oak Street', // city: 'Springfield', // postalCode: '12345' // } // ] // } // Generate user with specific overrides const adminUser = userFactory.build({ role: UserRole.ADMIN, isActive: true, addresses: [ { street: '123 Admin St', city: 'Admin City', postalCode: '12345' } ] }); console.log(adminUser); // Output: // { // firstName: 'Michael', // Generated // lastName: 'Brown', // Generated // email: 'michael.brown@example.com', // Generated // age: 35, // Generated (18-80 range) // birthDate: 2024-02-20T14:15:00.000Z, // Generated // isActive: true, // Overridden // role: 'admin', // Overridden // addresses: [ // Overridden // { street: '123 Admin St', city: 'Admin City', postalCode: '12345' } // ] // } // Generate multiple users const users = userFactory.buildMany(3); console.log(users); // Output: // [ // { // firstName: 'Sarah', // lastName: 'Wilson', // email: 'sarah.wilson@example.com', // age: 24, // birthDate: 2024-03-10T09:45:00.000Z, // isActive: false, // role: 'user', // addresses: undefined // }, // { // firstName: 'David', // lastName: 'Miller', // email: 'david.miller@example.com', // age: 42, // birthDate: 2024-01-05T16:20:00.000Z, // isActive: true, // role: 'moderator', // addresses: [ // { // street: '456 Pine Avenue', // city: 'Riverside', // postalCode: '67890' // } // ] // }, // { // firstName: 'Lisa', // lastName: 'Davis', // email: 'lisa.davis@example.com', // age: 31, // birthDate: 2024-02-28T11:10:00.000Z, // isActive: true, // role: 'user', // addresses: undefined // } // ] // Generate sequence with unique IDs const userSequence = userFactory.sequence(2, { role: UserRole.USER }); console.log(userSequence); // Output: // [ // { // firstName: 'Alex', // lastName: 'Taylor', // email: 'alex.taylor@example.com', // age: 29, // birthDate: 2024-01-12T13:25:00.000Z, // isActive: false, // role: 'user', // Overridden for all // addresses: undefined, // id: 1 // Auto-generated sequence ID // }, // { // firstName: 'Jessica', // lastName: 'Anderson', // email: 'jessica.anderson@example.com', // age: 26, // birthDate: 2024-03-15T08:50:00.000Z, // isActive: true, // role: 'user', // Overridden for all // addresses: [ // { // street: '789 Elm Street', // city: 'Fairview', // postalCode: '54321' // } // ], // id: 2 // Auto-generated sequence ID // } // ] ``` ### 2. Plain TypeScript (without NestJS dependencies) ```typescript import { Factory, FakeProperty } from 'ts-forged'; class ProductDto { @FakeProperty() name!: string; @FakeProperty() description!: string; @FakeProperty() price!: number; @FakeProperty() inStock!: boolean; @FakeProperty() createdAt!: Date; } class OrderDto { @FakeProperty() orderId!: string; @FakeProperty() customerName!: string; @FakeProperty() totalAmount!: number; @FakeProperty(() => ProductDto) products!: ProductDto[]; @FakeProperty() orderDate!: Date; } // Create factories const productFactory = Factory.create(ProductDto); const orderFactory = Factory.create(OrderDto); // Generate fake data const product = productFactory.build(); console.log(product); // Output: // { // name: 'Wireless Headphones', // description: 'High-quality wireless headphones with noise cancellation', // price: 129.99, // inStock: true, // createdAt: 2024-01-20T12:30:00.000Z // } const order = orderFactory.build(); console.log(order); // Output: // { // orderId: 'ORD-2024-001', // customerName: 'Robert Johnson', // totalAmount: 299.97, // products: [ // { // name: 'Smartphone', // description: 'Latest smartphone with advanced features', // price: 199.99, // inStock: true, // createdAt: 2024-01-15T10:20:00.000Z // }, // { // name: 'Phone Case', // description: 'Durable protective case for smartphones', // price: 19.99, // inStock: false, // createdAt: 2024-01-18T14:45:00.000Z // } // ], // orderDate: 2024-01-25T09:15:00.000Z // } // Generate with nested object overrides const specificOrder = orderFactory.build({ customerName: 'John Doe', products: [ productFactory.build({ name: 'Laptop', price: 999.99 }), productFactory.build({ name: 'Mouse', price: 29.99 }) ] }); console.log(specificOrder); // Output: // { // orderId: 'ORD-2024-002', // Generated // customerName: 'John Doe', // Overridden // totalAmount: 1029.98, // Generated // products: [ // Overridden // { // name: 'Laptop', // Overridden // description: 'Portable computer for work and gaming', // Generated // price: 999.99, // Overridden // inStock: true, // Generated // createdAt: 2024-01-22T11:30:00.000Z // Generated // }, // { // name: 'Mouse', // Overridden // description: 'Wireless optical mouse with ergonomic design', // Generated // price: 29.99, // Overridden // inStock: false, // Generated // createdAt: 2024-01-23T16:20:00.000Z // Generated // } // ], // orderDate: 2024-01-26T13:45:00.000Z // Generated // } ``` ### 3. Advanced Usage with Custom Types ```typescript import { Factory, FakeProperty } from 'ts-forged'; enum OrderStatus { PENDING = 'pending', CONFIRMED = 'confirmed', SHIPPED = 'shipped', DELIVERED = 'delivered', CANCELLED = 'cancelled', } class CustomerDto { @FakeProperty() id!: string; @FakeProperty() name!: string; @FakeProperty() email!: string; @FakeProperty() phone!: string; } class OrderItemDto { @FakeProperty() productId!: string; @FakeProperty() quantity!: number; @FakeProperty() unitPrice!: number; @FakeProperty() totalPrice!: number; } class OrderDto { @FakeProperty() id!: string; @FakeProperty(() => CustomerDto) customer!: CustomerDto; @FakeProperty(() => OrderItemDto) items!: OrderItemDto[]; @FakeProperty() status!: OrderStatus; @FakeProperty() createdAt!: Date; @FakeProperty() updatedAt!: Date; } // Create factory const orderFactory = Factory.create(OrderDto); // Generate test data for different scenarios const pendingOrder = orderFactory.build({ status: OrderStatus.PENDING }); console.log(pendingOrder); // Output: // { // id: 'ORD-2024-003', // customer: { // id: 'CUST-001', // name: 'Alice Smith', // email: 'alice.smith@example.com', // phone: '+1-555-0123' // }, // items: [ // { // productId: 'PROD-001', // quantity: 2, // unitPrice: 49.99, // totalPrice: 99.98 // }, // { // productId: 'PROD-002', // quantity: 1, // unitPrice: 29.99, // totalPrice: 29.99 // } // ], // status: 'pending', // Overridden // createdAt: 2024-01-27T10:30:00.000Z, // updatedAt: 2024-01-27T10:30:00.000Z // } const confirmedOrder = orderFactory.build({ status: OrderStatus.CONFIRMED, items: [ { productId: 'prod-1', quantity: 2, unitPrice: 50, totalPrice: 100 }, { productId: 'prod-2', quantity: 1, unitPrice: 75, totalPrice: 75 } ] }); console.log(confirmedOrder); // Output: // { // id: 'ORD-2024-004', // customer: { // id: 'CUST-002', // name: 'Bob Johnson', // email: 'bob.johnson@example.com', // phone: '+1-555-0456' // }, // items: [ // Overridden // { productId: 'prod-1', quantity: 2, unitPrice: 50, totalPrice: 100 }, // { productId: 'prod-2', quantity: 1, unitPrice: 75, totalPrice: 75 } // ], // status: 'confirmed', // Overridden // createdAt: 2024-01-28T14:20:00.000Z, // updatedAt: 2024-01-28T14:20:00.000Z // } // Generate multiple orders with different statuses const orders = orderFactory.buildMany(3); console.log(orders); // Output: // [ // { // id: 'ORD-2024-005', // customer: { id: 'CUST-003', name: 'Carol Davis', email: 'carol.davis@example.com', phone: '+1-555-0789' }, // items: [ // { productId: 'PROD-003', quantity: 1, unitPrice: 89.99, totalPrice: 89.99 } // ], // status: 'shipped', // createdAt: 2024-01-29T09:15:00.000Z, // updatedAt: 2024-01-29T09:15:00.000Z // }, // { // id: 'ORD-2024-006', // customer: { id: 'CUST-004', name: 'David Wilson', email: 'david.wilson@example.com', phone: '+1-555-0321' }, // items: [ // { productId: 'PROD-004', quantity: 3, unitPrice: 19.99, totalPrice: 59.97 }, // { productId: 'PROD-005', quantity: 1, unitPrice: 149.99, totalPrice: 149.99 } // ], // status: 'delivered', // createdAt: 2024-01-30T16:45:00.000Z, // updatedAt: 2024-01-30T16:45:00.000Z // }, // { // id: 'ORD-2024-007', // customer: { id: 'CUST-005', name: 'Eva Brown', email: 'eva.brown@example.com', phone: '+1-555-0654' }, // items: [ // { productId: 'PROD-006', quantity: 1, unitPrice: 299.99, totalPrice: 299.99 } // ], // status: 'cancelled', // createdAt: 2024-01-31T11:30:00.000Z, // updatedAt: 2024-01-31T11:30:00.000Z // } // ] ``` ## API Reference ### Factory Class The main class for generating fake data. #### `Factory.create<T>(type: Type<T>): Factory<T>` Creates a new factory instance for the given type. ```typescript const factory = Factory.create(UserDto); ``` #### `factory.build(overrides?: Partial<T>): T` Builds a single instance with optional overrides. ```typescript const user = factory.build(); const specificUser = factory.build({ firstName: 'John', age: 30 }); ``` #### `factory.buildMany(count: number, overrides?: Partial<T>): T[]` Builds multiple instances with optional overrides applied to all. ```typescript const users = factory.buildMany(5); const activeUsers = factory.buildMany(3, { isActive: true }); ``` #### `factory.sequence(count: number, overrides?: Partial<T>): T[]` Creates a sequence of instances with unique IDs and optional overrides. ```typescript const userSequence = factory.sequence(3, { role: UserRole.USER }); ``` ### Decorators #### `@FakeProperty(typeFn?: () => any)` Marks a property for fake data generation. Optionally accepts a function that returns the type for nested objects or arrays. ```typescript class UserDto { @FakeProperty() name!: string; @FakeProperty(() => AddressDto) address!: AddressDto; @FakeProperty(() => ProductDto) products!: ProductDto[]; } ``` ## Supported Types The library automatically generates appropriate fake data for: - **Primitive Types**: `string`, `number`, `boolean`, `Date` - **Enums**: Automatically detected from `@ApiProperty({ enum: EnumType })` - **Arrays**: Supported with element type detection - **Nested Objects**: Supported with explicit type specification - **Optional Fields**: Detected from `@IsOptional()` decorator ## Validation Integration The library respects `class-validator` decorators: - `@IsEmail()` - Generates valid email addresses - `@IsDate()` - Generates Date objects - `@Min()` / `@Max()` - Respects number constraints - `@IsOptional()` - May generate `undefined` for optional fields - `@IsEnum()` - Generates values from the specified enum ## Testing Examples ```typescript import { Factory } from 'ts-forged'; describe('UserService', () => { let userFactory: Factory<UserDto>; beforeEach(() => { userFactory = Factory.create(UserDto); }); it('should create a user', async () => { const userData = userFactory.build(); console.log('Generated user data:', userData); // Output: Generated user data: { firstName: 'Emma', lastName: 'Johnson', email: 'emma.johnson@example.com', age: 28, ... } const result = await userService.createUser(userData); expect(result).toBeDefined(); expect(result.firstName).toBe(userData.firstName); }); it('should handle multiple users', async () => { const users = userFactory.buildMany(5); expect(users).toHaveLength(5); for (const user of users) { const result = await userService.createUser(user); expect(result).toBeDefined(); } }); it('should respect overrides', async () => { const adminUser = userFactory.build({ role: UserRole.ADMIN, isActive: true }); expect(adminUser.role).toBe(UserRole.ADMIN); expect(adminUser.isActive).toBe(true); }); }); ``` ## 🔧 Advanced Usage ### Custom Field Generators You can create custom generators for specific field types: ```typescript import { Factory } from 'ts-forged'; class CustomUserDto { firstName!: string; lastName!: string; customField!: string; } // The library automatically detects field names and generates appropriate data const factory = Factory.create(CustomUserDto); const user = factory.build(); // customField will be generated as a string with smart detection ``` ### Performance Optimization For large-scale testing, consider: ```typescript // Create factory once and reuse const userFactory = Factory.create(UserDto); // Generate in batches const users = userFactory.buildMany(1000); // Use sequences for unique IDs const userSequence = userFactory.sequence(100); ``` ### Integration with Testing Frameworks ```typescript // Jest example describe('User API', () => { const userFactory = Factory.create(UserDto); it('should create user', async () => { const userData = userFactory.build(); const response = await request(app) .post('/users') .send(userData); expect(response.status).toBe(201); expect(response.body.firstName).toBe(userData.firstName); }); }); // Vitest example import { describe, it, expect } from 'vitest'; describe('User Service', () => { const userFactory = Factory.create(UserDto); it('should validate user data', () => { const user = userFactory.build(); const validation = validateUser(user); expect(validation.isValid).toBe(true); }); }); ``` ## 🤝 Contributing Please see the [Contributing Guide](CONTRIBUTING.md) for details. ### Development Setup ```bash # Clone the repository git clone https://github.com/Tyler-Churchill/ts-forged.git cd ts-forged # Install dependencies npm install # Run tests npm test # Run tests in watch mode npm run test:watch # Build the project npm run build # Lint and format npm run lint npm run format ``` ### Running Tests ```bash # Run all tests npm test # Run tests with coverage npm run test:coverage # Run tests in watch mode npm run test:watch ``` ## 📄 License This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. ## 🙏 Acknowledgments - [@faker-js/faker](https://github.com/faker-js/faker) for realistic fake data generation - [class-validator](https://github.com/typestack/class-validator) for validation decorators - [NestJS](https://nestjs.com/) for the excellent framework and decorators - The TypeScript community for inspiration and best practices ## 📊 Performance - **Bundle Size**: ~13KB minified + gzipped - **Generation Speed**: ~1000 objects/second - **Memory Usage**: Minimal with tree-shaking support - **TypeScript Support**: Full type safety with zero runtime overhead ## 🔗 Related Projects - [@faker-js/faker](https://github.com/faker-js/faker) - Generate massive amounts of fake (but realistic) data for testing and development. ## 📈 Roadmap - [ ] Custom field generators - [ ] Database integration - [ ] GraphQL schema support - [ ] Performance benchmarks - [ ] More validation decorators - [ ] Plugin system ---