$npx -y skills add girijashankarj/cursor-handbook --skill test-data-factoryGenerate type-safe test data factories and fixtures for unit and integration tests. Use when the user asks to create mock data, test fixtures, or data factories.
| 1 | # Skill: Test Data Factory |
| 2 | |
| 3 | Create reusable, type-safe factory functions for generating test data with sensible defaults and easy overrides. |
| 4 | |
| 5 | ## Trigger |
| 6 | When the user asks to create test data, mock data, fixtures, factories, or seed data for tests. |
| 7 | |
| 8 | ## Prerequisites |
| 9 | - [ ] Entity/model to create factories for identified |
| 10 | - [ ] TypeScript interfaces or schema definitions available |
| 11 | - [ ] Testing framework known (Jest, Vitest, etc.) |
| 12 | |
| 13 | ## Steps |
| 14 | |
| 15 | ### Step 1: Identify Entities |
| 16 | - [ ] List all entities that need factories |
| 17 | - [ ] Identify relationships between entities (foreign keys, nested objects) |
| 18 | - [ ] Check existing factories in `tests/mocks/factories/` to avoid duplicates |
| 19 | |
| 20 | ### Step 2: Design Factory Structure |
| 21 | |
| 22 | ``` |
| 23 | tests/mocks/factories/ |
| 24 | ├── index.ts # Re-exports all factories |
| 25 | ├── user.factory.ts |
| 26 | ├── order.factory.ts |
| 27 | ├── product.factory.ts |
| 28 | └── helpers.ts # Shared helpers (randomId, timestamps, etc.) |
| 29 | ``` |
| 30 | |
| 31 | ### Step 3: Create Helper Utilities |
| 32 | |
| 33 | ```typescript |
| 34 | // tests/mocks/factories/helpers.ts |
| 35 | import { randomUUID } from 'crypto'; |
| 36 | |
| 37 | export const fakeId = () => randomUUID(); |
| 38 | export const fakeDate = (daysAgo = 0) => { |
| 39 | const d = new Date(); |
| 40 | d.setDate(d.getDate() - daysAgo); |
| 41 | return d.toISOString(); |
| 42 | }; |
| 43 | export const fakeMoney = (min = 1, max = 1000) => |
| 44 | Math.round((Math.random() * (max - min) + min) * 100) / 100; |
| 45 | export const fakeEmail = (name = 'test') => |
| 46 | `${name}-${Math.random().toString(36).slice(2, 8)}@example.com`; |
| 47 | export const pickRandom = <T>(arr: T[]): T => |
| 48 | arr[Math.floor(Math.random() * arr.length)]; |
| 49 | ``` |
| 50 | |
| 51 | ### Step 4: Create Entity Factory |
| 52 | |
| 53 | Template for each entity: |
| 54 | |
| 55 | ```typescript |
| 56 | // tests/mocks/factories/order.factory.ts |
| 57 | import { fakeId, fakeDate, fakeMoney } from './helpers'; |
| 58 | |
| 59 | interface Order { |
| 60 | id: string; |
| 61 | customerId: string; |
| 62 | status: 'PENDING' | 'CONFIRMED' | 'SHIPPED' | 'DELIVERED' | 'CANCELLED'; |
| 63 | total: number; |
| 64 | items: OrderItem[]; |
| 65 | createdAt: string; |
| 66 | updatedAt: string; |
| 67 | activeIndicator: boolean; |
| 68 | } |
| 69 | |
| 70 | type OrderOverrides = Partial<Order>; |
| 71 | |
| 72 | export function buildOrder(overrides: OrderOverrides = {}): Order { |
| 73 | return { |
| 74 | id: fakeId(), |
| 75 | customerId: fakeId(), |
| 76 | status: 'PENDING', |
| 77 | total: fakeMoney(10, 500), |
| 78 | items: [], |
| 79 | createdAt: fakeDate(), |
| 80 | updatedAt: fakeDate(), |
| 81 | activeIndicator: true, |
| 82 | ...overrides, |
| 83 | }; |
| 84 | } |
| 85 | |
| 86 | export function buildOrders(count: number, overrides: OrderOverrides = {}): Order[] { |
| 87 | return Array.from({ length: count }, () => buildOrder(overrides)); |
| 88 | } |
| 89 | |
| 90 | // Pre-built scenarios |
| 91 | export const confirmedOrder = (overrides: OrderOverrides = {}) => |
| 92 | buildOrder({ status: 'CONFIRMED', ...overrides }); |
| 93 | |
| 94 | export const cancelledOrder = (overrides: OrderOverrides = {}) => |
| 95 | buildOrder({ status: 'CANCELLED', activeIndicator: false, ...overrides }); |
| 96 | ``` |
| 97 | |
| 98 | ### Step 5: Handle Relationships |
| 99 | |
| 100 | ```typescript |
| 101 | // For entities with relationships: |
| 102 | export function buildOrderWithItems( |
| 103 | itemCount = 3, |
| 104 | overrides: OrderOverrides = {} |
| 105 | ): Order { |
| 106 | const items = buildOrderItems(itemCount); |
| 107 | const total = items.reduce((sum, item) => sum + item.price * item.quantity, 0); |
| 108 | return buildOrder({ items, total, ...overrides }); |
| 109 | } |
| 110 | ``` |
| 111 | |
| 112 | ### Step 6: Create Database Fixtures (for integration tests) |
| 113 | |
| 114 | ```typescript |
| 115 | // tests/mocks/factories/db-fixtures.ts |
| 116 | export async function seedTestUser(db: Database, overrides = {}) { |
| 117 | const user = buildUser(overrides); |
| 118 | await db.query( |
| 119 | 'INSERT INTO users (id, email, name, created_at) VALUES ($1, $2, $3, $4)', |
| 120 | [user.id, user.email, user.name, user.createdAt] |
| 121 | ); |
| 122 | return user; |
| 123 | } |
| 124 | |
| 125 | export async function cleanupTestData(db: Database) { |
| 126 | await db.query('DELETE FROM order_items WHERE id LIKE $1', ['test-%']); |
| 127 | await db.query('DELETE FROM orders WHERE id LIKE $1', ['test-%']); |
| 128 | await db.query('DELETE FROM users WHERE id LIKE $1', ['test-%']); |
| 129 | } |
| 130 | ``` |
| 131 | |
| 132 | ### Step 7: Create Index File |
| 133 | |
| 134 | ```typescript |
| 135 | // tests/mocks/factories/index.ts |
| 136 | export * from './helpers'; |
| 137 | export * from './user.factory'; |
| 138 | export * from './order.factory'; |
| 139 | export * from './product.factory'; |
| 140 | ``` |
| 141 | |
| 142 | ### Step 8: Validate |
| 143 | - [ ] Factories produce valid entities (pass schema validation) |
| 144 | - [ ] Overrides work correctly (custom values replace defaults) |
| 145 | - [ ] Batch creation works (`buildOrders(10)`) |
| 146 | - [ ] Scenario builders produce correct states |
| 147 | - [ ] No real PII in factory defaults (use fake data) |
| 148 | |
| 149 | ## Rules |
| 150 | - **ALWAYS** use `build` prefix for factory functions (not `create` — reserve that for DB operations) |
| 151 | - **ALWAYS** return plain objects (not class instances) for unit test factories |
| 152 | - **ALWAYS** make every field overridable via the overrides parameter |
| 153 | - **NEVER** use real PII — use fake emails (`@example.com`), fake names, fake IDs |
| 154 | - **NEVER** use `Math.random()` for IDs — use `randomUUID()` or sequential IDs |
| 155 | - Defaults should produce a valid entity that passes validation |
| 156 | - Use scenario |