@seedts/testing
v0.1.1
Published
Testing utilities for SeedTS - Snapshot testing, deterministic seeding, and test helpers
Maintainers
Readme
@seedts/testing
Testing utilities for SeedTS - Snapshot testing, deterministic seeding, and test helpers.
Installation
npm install --save-dev @seedts/testing
# or
pnpm add -D @seedts/testing
# or
yarn add --dev @seedts/testingFeatures
- 📸 Snapshot Testing - Create and compare snapshots of seeded data
- 🎲 Deterministic Seeding - Generate consistent data for reliable tests
- 🧪 Test Helpers - Utilities for asserting and validating seed results
- ⚡ Framework Integration - Jest and Vitest support
- 🔧 Flexible - Works with any test framework
Quick Start
Snapshot Testing
import { snapshot } from '@seedts/testing';
import { Executor } from '@seedts/core';
test('users seed generates correct data', async () => {
const executor = new Executor([UsersSeed]);
const results = await executor.execute();
const result = await snapshot('users', results[0].data);
expect(result.pass).toBe(true);
});Deterministic Seeding
import { withDeterministicContext } from '@seedts/testing';
test('users seed with deterministic data', async () => {
await withDeterministicContext(async (ctx) => {
// Math.random and Date.now() are now deterministic
const executor = new Executor([UsersSeed]);
const results = await executor.execute();
// Results will be identical on every run
expect(results[0].data[0].createdAt).toEqual(new Date('2024-01-01T00:00:00Z'));
}, { seed: 42 });
});Snapshot Testing
Creating Snapshots
import { snapshot } from '@seedts/testing';
const result = await snapshot('users', usersData, {
snapshotDir: '__snapshots__',
pretty: true
});
if (result.isNew) {
console.log('New snapshot created:', result.snapshotPath);
}Comparing Snapshots
const result = await snapshot('users', usersData);
if (!result.pass) {
console.error('Snapshot mismatch:');
console.error(result.diff?.diff);
}Updating Snapshots
// Programmatically
const result = await snapshot('users', usersData, {
updateSnapshot: true
});
// Via environment variable
// UPDATE_SNAPSHOTS=true npm testSnapshot Options
interface SnapshotOptions {
snapshotDir?: string; // Default: '__snapshots__'
snapshotName?: string; // Default: seed name
pretty?: boolean; // Default: true
updateSnapshot?: boolean; // Default: false
excludeFields?: string[]; // Fields to exclude
serializer?: (data: any) => string; // Custom serializer
normalize?: (data: any[]) => any[]; // Data normalizer
}Normalizing Data
import { snapshot, normalizeSeedResult } from '@seedts/testing';
// Remove timestamps and sort by ID for consistent snapshots
const result = await snapshot('users', usersData, {
normalize: normalizeSeedResult
});
// Custom normalizer
const result = await snapshot('products', productsData, {
normalize: (data) => data.sort((a, b) => a.sku.localeCompare(b.sku))
});Excluding Fields
const result = await snapshot('users', usersData, {
excludeFields: ['createdAt', 'updatedAt', 'id']
});Deterministic Seeding
Seeded Random Number Generator
import { SeededRandom } from '@seedts/testing';
const random = new SeededRandom(42);
random.next(); // 0-1 random number
random.int(1, 100); // Random integer 1-100
random.float(0, 1); // Random float 0-1
random.pick(['a', 'b', 'c']); // Random array element
random.shuffle([1, 2, 3]); // Shuffle array
random.boolean(); // Random booleanDeterministic Context
import { createDeterministicContext } from '@seedts/testing';
const ctx = createDeterministicContext({
seed: 42,
freezeTime: true,
timestamp: new Date('2024-01-01T00:00:00Z')
});
// Use in factories
const factory = () => ({
name: ctx.random.pick(['Alice', 'Bob', 'Charlie']),
age: ctx.random.int(18, 80),
createdAt: ctx.getDate(),
score: ctx.random.float(0, 100)
});
// Always restore after use
ctx.restore();Deterministic Execution
import { withDeterministicContext } from '@seedts/testing';
await withDeterministicContext(async (ctx) => {
// Everything inside is deterministic
const data = Array.from({ length: 10 }, (_, i) => ({
id: i + 1,
value: ctx.random.int(1, 100),
timestamp: ctx.getDate()
}));
return data;
}, { seed: 42 });
// Context automatically restoredDeterministic Faker
import { setupDeterministicFaker } from '@seedts/testing';
import { faker } from '@faker-js/faker';
const { restore } = setupDeterministicFaker(42);
// Faker will generate consistent data
const name = faker.person.fullName(); // Always same for seed 42
const email = faker.internet.email(); // Always same for seed 42
restore(); // Restore Math.randomDeterministic Generators
import {
DeterministicIdGenerator,
createDeterministicUUID,
createDeterministicEmail,
createDeterministicUsername
} from '@seedts/testing';
// ID generator
const idGen = new DeterministicIdGenerator(1);
idGen.next(); // 1
idGen.next(); // 2
idGen.next(); // 3
// UUID generator
const uuidGen = createDeterministicUUID();
uuidGen(); // '00000000-0000-0000-0000-000000000001'
uuidGen(); // '00000000-0000-0000-0000-000000000002'
// Email generator
const emailGen = createDeterministicEmail();
emailGen(); // '[email protected]'
emailGen(); // '[email protected]'
// Username generator
const usernameGen = createDeterministicUsername();
usernameGen(); // 'user0'
usernameGen(); // 'user1'Test Helpers
Execution Helpers
import { executeSeedsAsMap } from '@seedts/testing';
const executor = new Executor([UsersSeed, PostsSeed]);
const results = await executeSeedsAsMap(executor);
// Access by name
const users = results.get('users');
const posts = results.get('posts');Assertions
import {
assertSeedSuccess,
assertAllSeedsSuccess,
assertSeedCount,
assertSeedData
} from '@seedts/testing';
const results = await executor.execute();
// Assert single seed succeeded
assertSeedSuccess(results[0]);
// Assert all seeds succeeded
assertAllSeedsSuccess(results);
// Assert record count
assertSeedCount(results[0], 10);
// Assert data validity
assertSeedData(results[0], (user) => {
return user.email.includes('@') && user.age >= 18;
});Data Validation
import { createValidator, assertSeedData } from '@seedts/testing';
const validateUser = createValidator<User>({
email: (v) => typeof v === 'string' && v.includes('@'),
age: (v) => typeof v === 'number' && v >= 0 && v <= 150,
name: (v) => typeof v === 'string' && v.length > 0
});
assertSeedData(usersResult, validateUser);Performance Assertions
import { assertSeedDuration } from '@seedts/testing';
const results = await executor.execute();
// Assert completed within 1 second
assertSeedDuration(results[0], 1000);Test Framework Integration
Vitest Integration
import { expect, describe, it } from 'vitest';
import { extendVitestMatchers } from '@seedts/testing/vitest';
// Extend Vitest matchers
extendVitestMatchers(expect);
describe('Seed Snapshots', () => {
it('users seed matches snapshot', async () => {
const executor = new Executor([UsersSeed]);
const results = await executor.execute();
await expect(results[0].data).toMatchSeedSnapshot('users');
});
});Jest Integration
import { expect, describe, it } from '@jest/globals';
import { extendJestMatchers } from '@seedts/testing/jest';
// Extend Jest matchers
extendJestMatchers(expect);
describe('Seed Snapshots', () => {
it('users seed matches snapshot', async () => {
const executor = new Executor([UsersSeed]);
const results = await executor.execute();
await expect(results[0].data).toMatchSeedSnapshot('users');
});
});Updating Snapshots
# Vitest
UPDATE_SNAPSHOTS=true npm test
# Jest
npm test -- --updateSnapshot
# or
npm test -- -uComplete Examples
Example 1: Snapshot Testing with Deterministic Data
import { describe, it, expect } from 'vitest';
import { Executor } from '@seedts/core';
import { MemoryAdapter } from '@seedts/memory';
import {
withDeterministicContext,
snapshot,
normalizeSeedResult
} from '@seedts/testing';
describe('Users Seed', () => {
it('generates consistent snapshot', async () => {
await withDeterministicContext(async () => {
const adapter = new MemoryAdapter();
const executor = new Executor([
(props) => UsersSeed({ ...props, adapter })
]);
const results = await executor.execute();
const usersData = results[0].data;
const result = await snapshot('users-deterministic', usersData, {
normalize: normalizeSeedResult,
excludeFields: ['id']
});
expect(result.pass).toBe(true);
}, { seed: 42 });
});
});Example 2: Testing Multiple Seeds
import {
Executor,
executeSeedsAsMap,
assertAllSeedsSuccess,
assertSeedCount
} from '@seedts/testing';
describe('All Seeds', () => {
it('execute successfully', async () => {
const adapter = new MemoryAdapter();
const executor = new Executor([
(props) => UsersSeed({ ...props, adapter }),
(props) => PostsSeed({ ...props, adapter }),
(props) => CommentsSeed({ ...props, adapter })
]);
const results = await executeSeedsAsMap(executor);
// Assert all succeeded
assertAllSeedsSuccess(Array.from(results.values()));
// Assert counts
assertSeedCount(results.get('users')!, 10);
assertSeedCount(results.get('posts')!, 25);
assertSeedCount(results.get('comments')!, 100);
});
});Example 3: Deterministic Faker Integration
import { setupDeterministicFaker } from '@seedts/testing';
import { faker } from '@faker-js/faker';
describe('Deterministic Faker', () => {
it('generates consistent data', () => {
const { restore } = setupDeterministicFaker(42);
const users = Array.from({ length: 5 }, () => ({
name: faker.person.fullName(),
email: faker.internet.email(),
age: faker.number.int({ min: 18, max: 80 })
}));
// These will be the same on every run
expect(users[0].name).toBe('Expected Name'); // Based on seed 42
expect(users.length).toBe(5);
restore();
});
});Example 4: Custom Validators
import { createValidator, assertSeedData } from '@seedts/testing';
describe('Data Validation', () => {
it('validates user data structure', async () => {
const validateUser = createValidator<User>({
id: (v) => typeof v === 'number' && v > 0,
email: (v) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v),
age: (v) => Number.isInteger(v) && v >= 18 && v <= 120,
name: (v) => typeof v === 'string' && v.length >= 2,
createdAt: (v) => v instanceof Date || typeof v === 'string'
});
const executor = new Executor([UsersSeed]);
const results = await executor.execute();
assertSeedData(results[0], validateUser);
});
});Example 5: Snapshot Diff Workflow
import { snapshot } from '@seedts/testing';
describe('Snapshot Workflow', () => {
it('handles snapshot lifecycle', async () => {
const data = [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' }
];
// First run - creates snapshot
let result = await snapshot('test-data', data);
expect(result.isNew).toBe(true);
// Second run - matches snapshot
result = await snapshot('test-data', data);
expect(result.pass).toBe(true);
// Modified data - shows diff
const modifiedData = [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Charlie' } // Changed from Bob
];
result = await snapshot('test-data', modifiedData);
expect(result.pass).toBe(false);
expect(result.diff).toBeDefined();
console.log(result.diff?.diff);
// Update snapshot
result = await snapshot('test-data', modifiedData, {
updateSnapshot: true
});
expect(result.wasUpdated).toBe(true);
});
});API Reference
Snapshot Functions
snapshot(name, data, options?)- Create or compare snapshotupdateSnapshot(name, data, options?)- Update existing snapshotdeleteSnapshot(name, options?)- Delete snapshot filelistSnapshots(dir?)- List all snapshotsclearSnapshots(dir?)- Clear all snapshotscreateSnapshotAssertion(data, testName, options?)- Create assertion helpershouldUpdateSnapshots()- Check if UPDATE_SNAPSHOTS=truenormalizeSeedResult(data)- Normalize data for snapshots
Deterministic Functions
SeededRandom(seed?)- Seeded random number generator classDeterministicContext(options?)- Context with frozen time and seeded randomcreateDeterministicContext(options?)- Create deterministic contextwithDeterministicContext(fn, options?)- Execute with deterministic contextsetupDeterministicFaker(seed?)- Make Faker.js deterministicDeterministicIdGenerator(start?)- Sequential ID generatorcreateDeterministicUUID()- Deterministic UUID generatorcreateDeterministicEmail()- Deterministic email generatorcreateDeterministicUsername()- Deterministic username generator
Test Helpers
executeSeedsAsMap(executor)- Execute and return results as mapassertSeedSuccess(result)- Assert seed succeededassertAllSeedsSuccess(results)- Assert all seeds succeededgetSeedByName(results, name)- Get seed by nameassertSeedCount(result, count)- Assert record countassertSeedData(result, predicate)- Assert data validityassertSeedDuration(result, maxMs)- Assert execution timecreateValidator(rules)- Create data validatorwaitFor(condition, timeout?, interval?)- Wait for conditiondeepEqual(a, b)- Deep equality comparison
TypeScript Support
All functions are fully typed:
import type {
SnapshotOptions,
SnapshotResult,
SnapshotDiff,
DeterministicOptions
} from '@seedts/testing';License
MIT
