Test Data Factory — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited Test Data Factory (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
You are an expert QA engineer specializing in test data management, factory patterns, and deterministic data generation strategies. When the user asks you to create test data factories, set up database seeding, implement builder patterns, or establish data management strategies for testing, follow these detailed instructions.
tests/
factories/
base.factory.ts
user.factory.ts
product.factory.ts
order.factory.ts
review.factory.ts
address.factory.ts
index.ts
builders/
user.builder.ts
order.builder.ts
query.builder.ts
seeders/
database-seeder.ts
api-seeder.ts
test-environment.ts
traits/
user-traits.ts
order-traits.ts
fixtures/
static/
countries.json
currencies.json
snapshots/
seed-data.sql
helpers/
cleanup.ts
id-generator.ts
date-generator.ts
config/
factory.config.tsThe base factory provides the foundation for all entity factories. It handles defaults, overrides, sequences, traits, and lifecycle hooks.
import { faker } from '@faker-js/faker';
type FactoryCallback<T> = (overrides?: Partial<T>) => T;
type AfterCreateHook<T> = (entity: T) => Promise<void>;
type TraitDefinition<T> = Partial<T> | (() => Partial<T>);
interface FactoryOptions {
seed?: number;
}
abstract class BaseFactory<T> {
protected faker = faker;
private afterCreateHooks: AfterCreateHook<T>[] = [];
private sequences: Map<string, number> = new Map();
private registeredTraits: Map<string, TraitDefinition<T>> = new Map();
constructor(options?: FactoryOptions) {
if (options?.seed !== undefined) {
this.faker.seed(options.seed);
}
}
/**
* Define the default shape of the entity.
* Subclasses must implement this method.
*/
abstract definition(overrides?: Partial<T>): T;
/**
* Build a single entity without persistence.
*/
build(overrides?: Partial<T>): T {
return { ...this.definition(), ...overrides } as T;
}
/**
* Build multiple entities without persistence.
*/
buildMany(count: number, overrides?: Partial<T>): T[] {
return Array.from({ length: count }, () => this.build(overrides));
}
/**
* Build an entity with named traits applied.
*/
buildWith(...traitNames: string[]): T {
let merged: Partial<T> = {};
for (const name of traitNames) {
const trait = this.registeredTraits.get(name);
if (!trait) {
throw new Error(`Unknown trait: "${name}". Register it with registerTrait().`);
}
const traitValue = typeof trait === 'function' ? trait() : trait;
merged = { ...merged, ...traitValue };
}
return this.build(merged);
}
/**
* Build and persist an entity using registered hooks.
*/
async create(overrides?: Partial<T>): Promise<T> {
const entity = this.build(overrides);
for (const hook of this.afterCreateHooks) {
await hook(entity);
}
return entity;
}
/**
* Build and persist multiple entities.
*/
async createMany(count: number, overrides?: Partial<T>): Promise<T[]> {
const entities: T[] = [];
for (let i = 0; i < count; i++) {
entities.push(await this.create(overrides));
}
return entities;
}
/**
* Register a lifecycle hook that runs after create().
*/
afterCreate(hook: AfterCreateHook<T>): this {
this.afterCreateHooks.push(hook);
return this;
}
/**
* Register a named trait for reuse across tests.
*/
registerTrait(name: string, definition: TraitDefinition<T>): this {
this.registeredTraits.set(name, definition);
return this;
}
/**
* Get the next value in a named sequence.
*/
protected sequence(name: string): number {
const current = this.sequences.get(name) || 0;
const next = current + 1;
this.sequences.set(name, next);
return next;
}
/**
* Reset all sequences. Call between test suites.
*/
resetSequences(): void {
this.sequences.clear();
}
}interface User {
id: string;
email: string;
name: string;
role: 'admin' | 'editor' | 'viewer';
status: 'active' | 'inactive' | 'suspended';
avatar?: string;
phone?: string;
address?: Address;
preferences: UserPreferences;
createdAt: Date;
updatedAt: Date;
}
interface UserPreferences {
theme: 'light' | 'dark';
language: string;
notifications: boolean;
timezone: string;
}
interface Address {
street: string;
city: string;
state: string;
zip: string;
country: string;
}
class UserFactory extends BaseFactory<User> {
constructor(options?: FactoryOptions) {
super(options);
this.registerDefaultTraits();
}
definition(): User {
const firstName = this.faker.person.firstName();
const lastName = this.faker.person.lastName();
const seq = this.sequence('user');
const now = new Date();
return {
id: this.faker.string.uuid(),
email: `test-user-${seq}@example.com`,
name: `${firstName} ${lastName}`,
role: 'viewer',
status: 'active',
avatar: this.faker.image.avatar(),
phone: this.faker.phone.number(),
preferences: {
theme: 'light',
language: 'en',
notifications: true,
timezone: 'America/New_York',
},
createdAt: now,
updatedAt: now,
};
}
private registerDefaultTraits(): void {
this.registerTrait('admin', { role: 'admin' });
this.registerTrait('editor', { role: 'editor' });
this.registerTrait('inactive', { status: 'inactive' });
this.registerTrait('suspended', { status: 'suspended' });
this.registerTrait('darkMode', {
preferences: {
theme: 'dark',
language: 'en',
notifications: true,
timezone: 'America/New_York',
},
});
this.registerTrait('withAddress', () => ({
address: {
street: this.faker.location.streetAddress(),
city: this.faker.location.city(),
state: this.faker.location.state(),
zip: this.faker.location.zipCode(),
country: 'US',
},
}));
}
}
// Usage examples
const userFactory = new UserFactory({ seed: 42 });
// Simple user with defaults
const user = userFactory.build();
// Admin user
const admin = userFactory.build({ role: 'admin' });
// User with traits
const darkModeAdmin = userFactory.buildWith('admin', 'darkMode');
// Multiple users
const users = userFactory.buildMany(10, { status: 'active' });
// User with specific overrides
const specificUser = userFactory.build({
email: '[email protected]',
name: 'Test User',
role: 'editor',
});When objects have many optional fields or complex construction logic, the builder pattern provides a fluent API that is more readable than nested overrides.
class UserBuilder {
private data: Partial<User> = {};
private readonly factory = new UserFactory();
constructor() {
// Start with factory defaults
this.data = this.factory.build();
}
static create(): UserBuilder {
return new UserBuilder();
}
withId(id: string): this {
this.data.id = id;
return this;
}
withName(name: string): this {
this.data.name = name;
return this;
}
withEmail(email: string): this {
this.data.email = email;
return this;
}
withRole(role: User['role']): this {
this.data.role = role;
return this;
}
asAdmin(): this {
this.data.role = 'admin';
return this;
}
asEditor(): this {
this.data.role = 'editor';
return this;
}
withStatus(status: User['status']): this {
this.data.status = status;
return this;
}
active(): this {
this.data.status = 'active';
return this;
}
inactive(): this {
this.data.status = 'inactive';
return this;
}
suspended(): this {
this.data.status = 'suspended';
return this;
}
withAddress(overrides?: Partial<Address>): this {
this.data.address = {
street: overrides?.street ?? faker.location.streetAddress(),
city: overrides?.city ?? faker.location.city(),
state: overrides?.state ?? faker.location.state(),
zip: overrides?.zip ?? faker.location.zipCode(),
country: overrides?.country ?? 'US',
};
return this;
}
withPreferences(overrides: Partial<UserPreferences>): this {
this.data.preferences = {
...this.data.preferences!,
...overrides,
};
return this;
}
createdDaysAgo(days: number): this {
const date = new Date();
date.setDate(date.getDate() - days);
this.data.createdAt = date;
return this;
}
build(): User {
return this.data as User;
}
}
// Usage
const adminUser = UserBuilder.create()
.asAdmin()
.active()
.withName('Admin User')
.withEmail('[email protected]')
.withAddress({ country: 'US', state: 'CA' })
.build();interface Order {
id: string;
userId: string;
items: OrderItem[];
subtotal: number;
tax: number;
shipping: number;
total: number;
status: 'pending' | 'confirmed' | 'shipped' | 'delivered' | 'cancelled';
shippingAddress: Address;
paymentMethod: string;
createdAt: Date;
updatedAt: Date;
}
interface OrderItem {
productId: string;
productName: string;
quantity: number;
unitPrice: number;
totalPrice: number;
}
interface Product {
id: string;
name: string;
slug: string;
description: string;
price: number;
currency: string;
category: string;
tags: string[];
sku: string;
inventory: number;
status: 'draft' | 'active' | 'archived';
createdAt: Date;
}
class OrderBuilder {
private order: Partial<Order> = {};
private _items: OrderItem[] = [];
private userFactory = new UserFactory();
static create(): OrderBuilder {
return new OrderBuilder();
}
constructor() {
this.order = {
id: faker.string.uuid(),
userId: faker.string.uuid(),
status: 'pending',
shippingAddress: {
street: faker.location.streetAddress(),
city: faker.location.city(),
state: faker.location.state(),
zip: faker.location.zipCode(),
country: 'US',
},
paymentMethod: 'credit_card',
createdAt: new Date(),
updatedAt: new Date(),
};
}
forUser(userId: string): this {
this.order.userId = userId;
return this;
}
forNewUser(overrides?: Partial<User>): this {
const user = this.userFactory.build(overrides);
this.order.userId = user.id;
return this;
}
addItem(product: Product, quantity: number = 1): this {
const item: OrderItem = {
productId: product.id,
productName: product.name,
quantity,
unitPrice: product.price,
totalPrice: Math.round(product.price * quantity * 100) / 100,
};
this._items.push(item);
return this;
}
withStatus(status: Order['status']): this {
this.order.status = status;
return this;
}
pending(): this { return this.withStatus('pending'); }
confirmed(): this { return this.withStatus('confirmed'); }
shipped(): this { return this.withStatus('shipped'); }
delivered(): this { return this.withStatus('delivered'); }
cancelled(): this { return this.withStatus('cancelled'); }
withPayment(method: string): this {
this.order.paymentMethod = method;
return this;
}
withFreeShipping(): this {
this.order.shipping = 0;
return this;
}
build(): Order {
const items = this._items;
const subtotal = items.reduce((sum, item) => sum + item.totalPrice, 0);
const tax = Math.round(subtotal * 0.08 * 100) / 100;
const shipping = this.order.shipping ?? (subtotal > 100 ? 0 : 9.99);
const total = Math.round((subtotal + tax + shipping) * 100) / 100;
return {
...this.order,
items,
subtotal,
tax,
shipping,
total,
} as Order;
}
}class ProductFactory extends BaseFactory<Product> {
private readonly categories = [
'electronics', 'clothing', 'books', 'home', 'sports', 'toys',
];
definition(): Product {
const name = this.faker.commerce.productName();
const seq = this.sequence('product');
return {
id: this.faker.string.uuid(),
name,
slug: name.toLowerCase().replace(/\s+/g, '-') + `-${seq}`,
description: this.faker.commerce.productDescription(),
price: parseFloat(this.faker.commerce.price({ min: 1, max: 999 })),
currency: 'USD',
category: this.faker.helpers.arrayElement(this.categories),
tags: this.faker.helpers.arrayElements(
['new', 'sale', 'popular', 'limited', 'featured'],
{ min: 1, max: 3 }
),
sku: `SKU-${seq.toString().padStart(6, '0')}`,
inventory: this.faker.number.int({ min: 0, max: 500 }),
status: 'active',
createdAt: this.faker.date.past(),
};
}
}import { faker } from '@faker-js/faker';
// Seeded Faker for reproducible tests
function createSeededFaker(seed: number) {
const seededFaker = faker;
seededFaker.seed(seed);
return seededFaker;
}
// Domain-specific generators
const testDataGenerators = {
email: {
valid: () => faker.internet.email().toLowerCase(),
corporate: (domain: string) =>
`${faker.person.firstName().toLowerCase()}.${faker.person.lastName().toLowerCase()}@${domain}`,
disposable: () =>
`test+${faker.string.nanoid(8)}@example.com`,
international: () =>
faker.internet.email({ provider: 'beispiel.de' }),
},
password: {
strong: () =>
faker.internet.password({ length: 16, memorable: false, pattern: /[A-Za-z0-9!@#$%]/ }),
weak: () => '123456',
tooShort: () => faker.string.alpha(3),
maxLength: () => faker.string.alpha(128),
},
phone: {
us: () => faker.phone.number({ style: 'national' }),
international: () => faker.phone.number({ style: 'international' }),
invalid: () => '000-000-0000',
},
date: {
past: (years: number = 1) => faker.date.past({ years }),
future: (years: number = 1) => faker.date.future({ years }),
between: (from: Date, to: Date) => faker.date.between({ from, to }),
recent: (days: number = 7) => faker.date.recent({ days }),
birthday: (min: number = 18, max: number = 80) =>
faker.date.birthdate({ min, max, mode: 'age' }),
},
financial: {
creditCard: () => ({
number: faker.finance.creditCardNumber(),
cvv: faker.finance.creditCardCVV(),
issuer: faker.finance.creditCardIssuer(),
expiry: `${faker.number.int({ min: 1, max: 12 }).toString().padStart(2, '0')}/${faker.number.int({ min: 26, max: 30 })}`,
}),
amount: (min: number = 0.01, max: number = 9999.99) =>
parseFloat(faker.finance.amount({ min, max, dec: 2 })),
currency: () => faker.finance.currencyCode(),
},
};import { faker as fakerEN } from '@faker-js/faker/locale/en';
import { faker as fakerDE } from '@faker-js/faker/locale/de';
import { faker as fakerJA } from '@faker-js/faker/locale/ja';
type Locale = 'en' | 'de' | 'ja';
const fakerByLocale: Record<Locale, typeof fakerEN> = {
en: fakerEN,
de: fakerDE,
ja: fakerJA,
};
function buildLocalizedUser(locale: Locale): User {
const f = fakerByLocale[locale];
const firstName = f.person.firstName();
const lastName = f.person.lastName();
return {
id: f.string.uuid(),
email: f.internet.email({ firstName, lastName }).toLowerCase(),
name: `${firstName} ${lastName}`,
role: 'viewer',
status: 'active',
preferences: {
theme: 'light',
language: locale,
notifications: true,
timezone: locale === 'de' ? 'Europe/Berlin' : locale === 'ja' ? 'Asia/Tokyo' : 'America/New_York',
},
createdAt: new Date(),
updatedAt: new Date(),
};
}interface SeederConfig {
clearBefore: boolean;
seedCounts: {
users: number;
products: number;
orders: number;
reviews: number;
};
seed: number;
}
class DatabaseSeeder {
private db: any;
private config: SeederConfig;
private userFactory: UserFactory;
private productFactory: ProductFactory;
private createdIds: { users: string[]; products: string[]; orders: string[] };
constructor(db: any, config: SeederConfig) {
this.db = db;
this.config = config;
this.userFactory = new UserFactory({ seed: config.seed });
this.productFactory = new ProductFactory({ seed: config.seed + 1 });
this.createdIds = { users: [], products: [], orders: [] };
}
async seed(): Promise<void> {
if (this.config.clearBefore) {
await this.clear();
}
console.log('Seeding users...');
const users = await this.seedUsers();
console.log('Seeding products...');
const products = await this.seedProducts();
console.log('Seeding orders...');
await this.seedOrders(users, products);
console.log('Seeding reviews...');
await this.seedReviews(users, products);
console.log(`Seed complete. Created ${this.createdIds.users.length} users, ` +
`${this.createdIds.products.length} products, ` +
`${this.createdIds.orders.length} orders.`);
}
private async seedUsers(): Promise<User[]> {
const users = this.userFactory.buildMany(this.config.seedCounts.users);
// Ensure at least one admin for testing admin flows
users[0] = this.userFactory.build({
role: 'admin',
email: '[email protected]',
name: 'Test Admin',
});
for (const user of users) {
await this.db.insert('users', user);
this.createdIds.users.push(user.id);
}
return users;
}
private async seedProducts(): Promise<Product[]> {
const products = this.productFactory.buildMany(this.config.seedCounts.products);
for (const product of products) {
await this.db.insert('products', product);
this.createdIds.products.push(product.id);
}
return products;
}
private async seedOrders(users: User[], products: Product[]): Promise<void> {
for (let i = 0; i < this.config.seedCounts.orders; i++) {
const user = faker.helpers.arrayElement(users);
const orderProducts = faker.helpers.arrayElements(products, { min: 1, max: 4 });
const order = OrderBuilder.create()
.forUser(user.id)
.build();
// Add items from selected products
for (const product of orderProducts) {
order.items.push({
productId: product.id,
productName: product.name,
quantity: faker.number.int({ min: 1, max: 5 }),
unitPrice: product.price,
totalPrice: product.price * faker.number.int({ min: 1, max: 5 }),
});
}
await this.db.insert('orders', order);
this.createdIds.orders.push(order.id);
}
}
private async seedReviews(users: User[], products: Product[]): Promise<void> {
for (let i = 0; i < this.config.seedCounts.reviews; i++) {
const review = {
id: faker.string.uuid(),
userId: faker.helpers.arrayElement(users).id,
productId: faker.helpers.arrayElement(products).id,
rating: faker.number.int({ min: 1, max: 5 }),
title: faker.lorem.sentence(),
body: faker.lorem.paragraph(),
createdAt: faker.date.recent({ days: 90 }),
};
await this.db.insert('reviews', review);
}
}
async clear(): Promise<void> {
// Delete in reverse dependency order
await this.db.deleteAll('reviews');
await this.db.deleteAll('orders');
await this.db.deleteAll('products');
await this.db.deleteAll('users');
}
async cleanup(): Promise<void> {
// Clean up only records created by this seeder instance
for (const orderId of this.createdIds.orders) {
await this.db.delete('orders', orderId);
}
for (const productId of this.createdIds.products) {
await this.db.delete('products', productId);
}
for (const userId of this.createdIds.users) {
await this.db.delete('users', userId);
}
}
}import { beforeEach, afterEach, describe, it, expect } from 'vitest';
class TestDataTracker {
private created: { table: string; id: string }[] = [];
private db: any;
constructor(db: any) {
this.db = db;
}
track(table: string, id: string): void {
this.created.push({ table, id });
}
async cleanup(): Promise<void> {
// Clean up in reverse order to respect foreign keys
const reversed = [...this.created].reverse();
for (const record of reversed) {
try {
await this.db.delete(record.table, record.id);
} catch (error) {
console.warn(`Failed to clean up ${record.table}:${record.id}: ${error}`);
}
}
this.created = [];
}
}
// Usage in test suite
describe('Order Service', () => {
let tracker: TestDataTracker;
beforeEach(() => {
tracker = new TestDataTracker(db);
});
afterEach(async () => {
await tracker.cleanup();
});
it('should create an order', async () => {
const user = userFactory.build();
await db.insert('users', user);
tracker.track('users', user.id);
const product = productFactory.build();
await db.insert('products', product);
tracker.track('products', product.id);
const order = OrderBuilder.create()
.forUser(user.id)
.addItem(product, 2)
.build();
const result = await orderService.create(order);
tracker.track('orders', result.id);
expect(result.status).toBe('pending');
expect(result.items).toHaveLength(1);
});
});describe('Integration Tests', () => {
let transaction: any;
beforeEach(async () => {
transaction = await db.beginTransaction();
});
afterEach(async () => {
await transaction.rollback();
});
it('should process payment', async () => {
const user = await transaction.insert('users', userFactory.build());
const order = await transaction.insert('orders',
new OrderFactory().build({ userId: user.id })
);
const result = await paymentService.process(order.id, { db: transaction });
expect(result.success).toBe(true);
// No explicit cleanup needed -- transaction rolls back automatically
});
});# tests/factories/user_factory.py
import factory
from factory import fuzzy
from datetime import datetime, timedelta
from myapp.models import User, Address
class AddressFactory(factory.Factory):
class Meta:
model = Address
street = factory.Faker('street_address')
city = factory.Faker('city')
state = factory.Faker('state_abbr')
zip_code = factory.Faker('zipcode')
country = 'US'
class UserFactory(factory.Factory):
class Meta:
model = User
id = factory.Faker('uuid4')
email = factory.LazyAttributeSequence(lambda obj, n: f'user-{n}@example.com')
name = factory.LazyAttribute(lambda obj: f'{factory.Faker("first_name").generate()} {factory.Faker("last_name").generate()}')
role = 'viewer'
status = 'active'
created_at = factory.LazyFunction(datetime.utcnow)
updated_at = factory.LazyFunction(datetime.utcnow)
class Params:
admin = factory.Trait(role='admin')
inactive = factory.Trait(status='inactive')
with_address = factory.Trait(
address=factory.SubFactory(AddressFactory)
)
# Usage
user = UserFactory()
admin = UserFactory(admin=True)
inactive_user = UserFactory(inactive=True)
user_with_address = UserFactory(with_address=True)
ten_users = UserFactory.create_batch(10)# tests/conftest.py
import pytest
from tests.factories import UserFactory, ProductFactory, OrderFactory
@pytest.fixture
def user_factory():
"""Provide a user factory for the test."""
return UserFactory
@pytest.fixture
def admin_user(user_factory):
"""Create a pre-built admin user."""
return user_factory(admin=True, email='[email protected]')
@pytest.fixture
def sample_users(user_factory):
"""Create a batch of sample users."""
return user_factory.create_batch(5)
@pytest.fixture
def product_factory():
return ProductFactory
@pytest.fixture(autouse=True)
def cleanup_database(db_session):
"""Roll back all changes after each test."""
yield
db_session.rollback()
# Usage in tests
def test_user_creation(user_factory):
user = user_factory(name='Test User', role='editor')
assert user.role == 'editor'
assert user.status == 'active'
def test_admin_access(admin_user):
assert admin_user.role == 'admin'
assert admin_user.email == '[email protected]'type OrderState = 'pending' | 'confirmed' | 'shipped' | 'delivered' | 'cancelled';
interface StateTransition {
from: OrderState;
to: OrderState;
fields: Partial<Order>;
}
const orderTransitions: StateTransition[] = [
{ from: 'pending', to: 'confirmed', fields: { updatedAt: new Date() } },
{ from: 'confirmed', to: 'shipped', fields: { updatedAt: new Date() } },
{ from: 'shipped', to: 'delivered', fields: { updatedAt: new Date() } },
{ from: 'pending', to: 'cancelled', fields: { updatedAt: new Date() } },
{ from: 'confirmed', to: 'cancelled', fields: { updatedAt: new Date() } },
];
class StatefulOrderFactory {
private baseFactory = new OrderFactory();
buildInState(targetState: OrderState): Order {
const order = this.baseFactory.build({ status: targetState });
return order;
}
buildLifecycle(): Record<OrderState, Order> {
const base = this.baseFactory.build();
const states: OrderState[] = ['pending', 'confirmed', 'shipped', 'delivered', 'cancelled'];
const lifecycle: Record<string, Order> = {};
for (const state of states) {
lifecycle[state] = { ...base, status: state, id: faker.string.uuid() };
}
return lifecycle as Record<OrderState, Order>;
}
}
// Usage: get orders at every lifecycle stage for comprehensive testing
const statefulFactory = new StatefulOrderFactory();
const shippedOrder = statefulFactory.buildInState('shipped');
const lifecycle = statefulFactory.buildLifecycle();
describe('Order state transitions', () => {
const states = Object.entries(lifecycle);
for (const [state, order] of states) {
it(`should handle order in ${state} state`, () => {
expect(order.status).toBe(state);
});
}
});// tests/config/factory.config.ts
interface FactoryConfig {
defaultSeed: number;
locale: string;
defaultCounts: {
small: number;
medium: number;
large: number;
};
database: {
cleanupStrategy: 'transaction' | 'tracker' | 'truncate';
seedOnStart: boolean;
seedConfig: {
users: number;
products: number;
orders: number;
};
};
faker: {
seed: number;
locale: string;
};
}
export const factoryConfig: FactoryConfig = {
defaultSeed: 12345,
locale: 'en',
defaultCounts: {
small: 5,
medium: 25,
large: 100,
},
database: {
cleanupStrategy: 'transaction',
seedOnStart: true,
seedConfig: {
users: 10,
products: 50,
orders: 100,
},
},
faker: {
seed: 42,
locale: 'en',
},
};// tests/factories/index.ts
import { UserFactory } from './user.factory';
import { ProductFactory } from './product.factory';
import { OrderFactory } from './order.factory';
import { factoryConfig } from '../config/factory.config';
const seed = factoryConfig.defaultSeed;
export const factories = {
user: new UserFactory({ seed }),
product: new ProductFactory({ seed: seed + 1 }),
order: new OrderFactory({ seed: seed + 2 }),
};
// Convenience functions
export const buildUser = (overrides?: Partial<User>) =>
factories.user.build(overrides);
export const buildUsers = (count: number, overrides?: Partial<User>) =>
factories.user.buildMany(count, overrides);
export const buildProduct = (overrides?: Partial<Product>) =>
factories.product.build(overrides);
export const buildOrder = (overrides?: Partial<Order>) =>
factories.order.build(overrides);
// Reset all factory sequences between test suites
export function resetFactories(): void {
factories.user.resetSequences();
factories.product.resetSequences();
factories.order.resetSequences();
}UserFactory, OrderFactory, and ProductFactory instead of generic names. Each factory maps to one domain entity.{ role: 'admin', status: 'active', permissions: [...] } everywhere, create an admin trait that bundles these attributes.build() method should never insert into a database, call an API, or log to the console. Use create() for persistence and keep build() pure.email: '[email protected]', every test gets the same email, causing unique constraint violations. Always use sequences or Faker for fields that need uniqueness.{ preferences: { notifications: { email: { frequency: 'daily' } } } }, the factory is not decomposed enough. Create separate builders for nested objects.new Date() without seeding produce non-deterministic data. Use Faker's date generators with a seed, or freeze time in tests.build() returns valid objects, that traits apply correctly, and that sequences increment.'user-1' collide across test files and make it impossible to run tests in parallel.console.log(JSON.stringify(factory.build(), null, 2)) to verify the generated data matches expectations.build() returns valid objects, that traits apply correctly, and that sequences increment.admin and editor traits and both set the role field, the result depends on the order of application. Document trait conflicts in the factory.~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.