test-data-generation

Generate realistic, consistent test data using factories, fixtures, and fake data libraries. Use for test data, fixtures, mock data, faker, test builders, and seed data generation.

$ Installer

git clone https://github.com/aj-geddes/useful-ai-prompts /tmp/useful-ai-prompts && cp -r /tmp/useful-ai-prompts/skills/test-data-generation ~/.claude/skills/useful-ai-prompts

// tip: Run this command in your terminal to install the skill


name: test-data-generation description: Generate realistic, consistent test data using factories, fixtures, and fake data libraries. Use for test data, fixtures, mock data, faker, test builders, and seed data generation.

Test Data Generation

Overview

Test data generation creates realistic, consistent, and maintainable test data for automated testing. Well-designed test data reduces test brittleness, improves readability, and makes it easier to create diverse test scenarios.

When to Use

  • Creating fixtures for integration tests
  • Generating fake data for development databases
  • Building test data with complex relationships
  • Creating realistic user inputs for testing
  • Seeding test databases
  • Generating edge cases and boundary values
  • Building reusable test data factories

Instructions

1. Factory Pattern for Test Data

JavaScript/Jest with Factory Functions

// tests/factories/userFactory.js
const { faker } = require('@faker-js/faker');

class UserFactory {
  static build(overrides = {}) {
    return {
      id: faker.string.uuid(),
      email: faker.internet.email(),
      firstName: faker.person.firstName(),
      lastName: faker.person.lastName(),
      age: faker.number.int({ min: 18, max: 80 }),
      phone: faker.phone.number(),
      address: {
        street: faker.location.streetAddress(),
        city: faker.location.city(),
        state: faker.location.state(),
        zip: faker.location.zipCode(),
        country: 'USA'
      },
      role: 'user',
      isActive: true,
      createdAt: faker.date.past(),
      ...overrides
    };
  }

  static buildMany(count, overrides = {}) {
    return Array.from({ length: count }, () => this.build(overrides));
  }

  static buildAdmin(overrides = {}) {
    return this.build({
      role: 'admin',
      permissions: ['read', 'write', 'delete'],
      ...overrides
    });
  }

  static buildInactive(overrides = {}) {
    return this.build({
      isActive: false,
      deactivatedAt: faker.date.recent(),
      ...overrides
    });
  }
}

// tests/user.test.js
describe('User Service', () => {
  test('should create user with valid data', () => {
    const userData = UserFactory.build();
    const user = userService.create(userData);

    expect(user.email).toBe(userData.email);
    expect(user.isActive).toBe(true);
  });

  test('should handle admin users differently', () => {
    const admin = UserFactory.buildAdmin();
    expect(admin.role).toBe('admin');
    expect(admin.permissions).toContain('delete');
  });

  test('should process multiple users', () => {
    const users = UserFactory.buildMany(5);
    expect(users).toHaveLength(5);
    expect(new Set(users.map(u => u.email)).size).toBe(5); // All unique
  });
});

Python with Factory Boy

# tests/factories.py
import factory
from factory.faker import Faker
from datetime import datetime, timedelta
from app.models import User, Order, Product

class UserFactory(factory.Factory):
    class Meta:
        model = User

    id = factory.Sequence(lambda n: n)
    email = Faker('email')
    first_name = Faker('first_name')
    last_name = Faker('last_name')
    username = factory.LazyAttribute(
        lambda obj: f"{obj.first_name.lower()}.{obj.last_name.lower()}"
    )
    age = Faker('random_int', min=18, max=80)
    phone = Faker('phone_number')
    is_active = True
    role = 'user'
    created_at = Faker('date_time_this_year')

    class Params:
        # Traits for different user types
        admin = factory.Trait(
            role='admin',
            permissions=['read', 'write', 'delete']
        )
        inactive = factory.Trait(
            is_active=False,
            deactivated_at=factory.LazyFunction(datetime.now)
        )
        premium = factory.Trait(
            subscription='premium',
            subscription_end=factory.LazyFunction(
                lambda: datetime.now() + timedelta(days=365)
            )
        )

class ProductFactory(factory.Factory):
    class Meta:
        model = Product

    id = factory.Sequence(lambda n: n)
    name = Faker('commerce_product_name')
    description = Faker('text', max_nb_chars=200)
    price = Faker('pydecimal', left_digits=3, right_digits=2, positive=True)
    sku = factory.LazyAttribute(
        lambda obj: f"SKU-{obj.id:06d}"
    )
    stock = Faker('random_int', min=0, max=100)
    category = Faker('random_element', elements=['electronics', 'clothing', 'books'])
    is_available = factory.LazyAttribute(lambda obj: obj.stock > 0)

class OrderFactory(factory.Factory):
    class Meta:
        model = Order

    id = factory.Sequence(lambda n: n)
    user = factory.SubFactory(UserFactory)
    status = 'pending'
    total = Faker('pydecimal', left_digits=4, right_digits=2, positive=True)
    created_at = Faker('date_time_this_month')

    @factory.post_generation
    def products(self, create, extracted, **kwargs):
        """Add products to order after creation."""
        if not create:
            return

        if extracted:
            for product in extracted:
                self.products.add(product)
        else:
            # Add 1-3 random products by default
            count = kwargs.get('count', 3)
            self.products.add(*ProductFactory.build_batch(count))

# tests/test_orders.py
import pytest
from tests.factories import UserFactory, OrderFactory, ProductFactory

def test_create_order_with_products():
    """Test order creation with specific products."""
    products = ProductFactory.build_batch(3)
    order = OrderFactory.build(products=products)

    assert order.user is not None
    assert len(order.products) == 3
    assert order.status == 'pending'

def test_admin_user_permissions():
    """Test admin user has correct permissions."""
    admin = UserFactory.build(admin=True)

    assert admin.role == 'admin'
    assert 'delete' in admin.permissions

def test_inactive_user():
    """Test inactive user properties."""
    user = UserFactory.build(inactive=True)

    assert not user.is_active
    assert user.deactivated_at is not None

def test_bulk_user_creation():
    """Test creating multiple users at once."""
    users = UserFactory.build_batch(10, role='user')

    assert len(users) == 10
    assert all(u.role == 'user' for u in users)
    # All emails should be unique
    assert len(set(u.email for u in users)) == 10

2. Builder Pattern for Complex Objects

// tests/builders/OrderBuilder.ts
import { faker } from '@faker-js/faker';

export class OrderBuilder {
  private order: Partial<Order> = {
    id: faker.string.uuid(),
    status: 'pending',
    items: [],
    total: 0,
    createdAt: new Date(),
  };

  withId(id: string): this {
    this.order.id = id;
    return this;
  }

  withStatus(status: OrderStatus): this {
    this.order.status = status;
    return this;
  }

  withUser(user: User): this {
    this.order.userId = user.id;
    this.order.user = user;
    return this;
  }

  withItems(items: OrderItem[]): this {
    this.order.items = items;
    this.order.total = items.reduce((sum, item) => sum + item.price * item.quantity, 0);
    return this;
  }

  addItem(product: Product, quantity: number = 1): this {
    const item: OrderItem = {
      productId: product.id,
      product,
      quantity,
      price: product.price,
      subtotal: product.price * quantity,
    };

    this.order.items = [...(this.order.items || []), item];
    this.order.total = (this.order.total || 0) + item.subtotal;
    return this;
  }

  withShippingAddress(address: Address): this {
    this.order.shippingAddress = address;
    return this;
  }

  asPaid(): this {
    this.order.status = 'paid';
    this.order.paidAt = new Date();
    return this;
  }

  asShipped(): this {
    this.order.status = 'shipped';
    this.order.shippedAt = new Date();
    return this;
  }

  build(): Order {
    return this.order as Order;
  }
}

// Usage in tests
describe('Order Processing', () => {
  it('should calculate total correctly', () => {
    const product1 = ProductBuilder.aProduct().withPrice(10.00).build();
    const product2 = ProductBuilder.aProduct().withPrice(25.00).build();

    const order = new OrderBuilder()
      .withUser(UserBuilder.aUser().build())
      .addItem(product1, 2)  // $20
      .addItem(product2, 1)  // $25
      .build();

    expect(order.total).toBe(45.00);
    expect(order.items).toHaveLength(2);
  });

  it('should process paid orders', () => {
    const order = new OrderBuilder()
      .withUser(UserBuilder.aUser().build())
      .addItem(ProductBuilder.aProduct().build())
      .asPaid()
      .build();

    expect(order.status).toBe('paid');
    expect(order.paidAt).toBeDefined();
  });
});

3. Fixtures for Integration Tests

Jest/TypeScript with Database Fixtures

// tests/fixtures/database.ts
import { PrismaClient } from '@prisma/client';
import { UserFactory, ProductFactory, OrderFactory } from './factories';

export class DatabaseFixtures {
  constructor(private prisma: PrismaClient) {}

  async seed() {
    // Create users
    const users = await Promise.all(
      UserFactory.buildMany(10).map(userData =>
        this.prisma.user.create({ data: userData })
      )
    );

    // Create products
    const products = await Promise.all(
      ProductFactory.buildMany(20).map(productData =>
        this.prisma.product.create({ data: productData })
      )
    );

    // Create orders
    const orders = await Promise.all(
      OrderFactory.buildMany(15).map(orderData =>
        this.prisma.order.create({
          data: {
            ...orderData,
            userId: users[Math.floor(Math.random() * users.length)].id,
            items: {
              create: products.slice(0, 3).map(product => ({
                productId: product.id,
                quantity: Math.floor(Math.random() * 3) + 1,
                price: product.price,
              })),
            },
          },
        })
      )
    );

    return { users, products, orders };
  }

  async clear() {
    await this.prisma.orderItem.deleteMany();
    await this.prisma.order.deleteMany();
    await this.prisma.product.deleteMany();
    await this.prisma.user.deleteMany();
  }
}

// tests/setup.ts
import { PrismaClient } from '@prisma/client';
import { DatabaseFixtures } from './fixtures/database';

const prisma = new PrismaClient();
const fixtures = new DatabaseFixtures(prisma);

beforeAll(async () => {
  await fixtures.clear();
  await fixtures.seed();
});

afterAll(async () => {
  await fixtures.clear();
  await prisma.$disconnect();
});

pytest Fixtures

# tests/conftest.py
import pytest
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from tests.factories import UserFactory, ProductFactory, OrderFactory

@pytest.fixture(scope='session')
def engine():
    """Create database engine."""
    return create_engine('sqlite:///:memory:')

@pytest.fixture(scope='session')
def tables(engine):
    """Create all tables."""
    Base.metadata.create_all(engine)
    yield
    Base.metadata.drop_all(engine)

@pytest.fixture
def db_session(engine, tables):
    """Create database session for each test."""
    Session = sessionmaker(bind=engine)
    session = Session()
    yield session
    session.rollback()
    session.close()

@pytest.fixture
def sample_users(db_session):
    """Create sample users for testing."""
    users = UserFactory.build_batch(5)
    db_session.add_all(users)
    db_session.commit()
    return users

@pytest.fixture
def sample_products(db_session):
    """Create sample products for testing."""
    products = ProductFactory.build_batch(10)
    db_session.add_all(products)
    db_session.commit()
    return products

@pytest.fixture
def admin_user(db_session):
    """Create an admin user."""
    admin = UserFactory.build(admin=True)
    db_session.add(admin)
    db_session.commit()
    return admin

@pytest.fixture
def order_with_items(db_session, sample_users, sample_products):
    """Create an order with items."""
    order = OrderFactory.build(
        user=sample_users[0],
        products=sample_products[:3]
    )
    db_session.add(order)
    db_session.commit()
    return order

# Usage in tests
def test_user_orders(order_with_items):
    """Test user has correct orders."""
    user = order_with_items.user
    assert len(user.orders) == 1
    assert user.orders[0].id == order_with_items.id

4. Realistic Data Generation

// tests/helpers/dataGenerator.js
const { faker } = require('@faker-js/faker');

class DataGenerator {
  static generateCreditCard() {
    return {
      number: faker.finance.creditCardNumber('#### #### #### ####'),
      cvv: faker.finance.creditCardCVV(),
      expiry: faker.date.future().toISOString().slice(0, 7), // YYYY-MM
      type: faker.helpers.arrayElement(['visa', 'mastercard', 'amex']),
    };
  }

  static generateAddress() {
    return {
      street: faker.location.streetAddress(),
      city: faker.location.city(),
      state: faker.location.state(),
      zip: faker.location.zipCode(),
      country: faker.location.country(),
      coordinates: {
        lat: parseFloat(faker.location.latitude()),
        lng: parseFloat(faker.location.longitude()),
      },
    };
  }

  static generateDateRange(days = 30) {
    const endDate = new Date();
    const startDate = new Date();
    startDate.setDate(startDate.getDate() - days);
    return { startDate, endDate };
  }

  static generateTimeSeries(count, interval = 'day') {
    const data = [];
    const now = new Date();

    for (let i = count - 1; i >= 0; i--) {
      const date = new Date(now);
      if (interval === 'day') date.setDate(date.getDate() - i);
      if (interval === 'hour') date.setHours(date.getHours() - i);

      data.push({
        timestamp: date,
        value: faker.number.float({ min: 0, max: 100, precision: 0.01 }),
      });
    }

    return data;
  }

  static generateRealisticEmail(firstName, lastName, domain = 'example.com') {
    const patterns = [
      `${firstName}.${lastName}`,
      `${firstName}${lastName}`,
      `${firstName.charAt(0)}${lastName}`,
      `${firstName}_${lastName}`,
    ];

    const pattern = faker.helpers.arrayElement(patterns);
    return `${pattern.toLowerCase()}@${domain}`;
  }
}

module.exports = { DataGenerator };

Best Practices

✅ DO

  • Use faker libraries for realistic data
  • Create reusable factories for common objects
  • Make factories flexible with overrides
  • Generate unique values where needed (emails, IDs)
  • Use builders for complex object construction
  • Create fixtures for integration test setup
  • Generate edge cases (empty strings, nulls, boundaries)
  • Keep test data deterministic when possible

❌ DON'T

  • Hardcode test data in multiple places
  • Use production data in tests
  • Generate truly random data for reproducible tests
  • Create overly complex factory hierarchies
  • Ignore data relationships and constraints
  • Generate massive datasets for simple tests
  • Forget to clean up generated data
  • Use the same test data for all tests

Tools & Libraries

  • JavaScript: @faker-js/faker, fishery, rosie, casual
  • Python: factory_boy, faker, hypothesis
  • Java: Instancio, EasyRandom, JavaFaker, Mockito
  • Ruby: FactoryBot, Faker, Fabrication
  • Database: SQL fixtures, JSON fixtures, CSV imports

Example: Complete Test Data Setup

// tests/setup/testData.ts
import { faker } from '@faker-js/faker';

// Configure faker for deterministic tests
faker.seed(12345);

export const TestData = {
  users: {
    admin: () => ({
      email: 'admin@test.com',
      role: 'admin',
      permissions: ['read', 'write', 'delete'],
    }),
    regular: () => ({
      email: faker.internet.email(),
      role: 'user',
      isActive: true,
    }),
  },

  products: {
    inStock: (overrides = {}) => ({
      name: faker.commerce.productName(),
      price: parseFloat(faker.commerce.price()),
      stock: faker.number.int({ min: 10, max: 100 }),
      isAvailable: true,
      ...overrides,
    }),
    outOfStock: () => ({
      ...TestData.products.inStock(),
      stock: 0,
      isAvailable: false,
    }),
  },

  orders: {
    pending: (userId: string) => ({
      userId,
      status: 'pending',
      items: [],
      total: 0,
    }),
    completed: (userId: string) => ({
      userId,
      status: 'completed',
      completedAt: faker.date.recent(),
      items: [],
      total: faker.number.float({ min: 10, max: 1000 }),
    }),
  },
};

Examples

See also: integration-testing, mocking-stubbing, continuous-testing skills for using test data effectively.