@kitiumai/jest-helpers
Version:
Jest unit and integration test helpers for enterprise testing - mocks, fixtures, matchers, and more
959 lines (672 loc) • 22.7 kB
Markdown
# @kitiumai/jest-helpers
**Jest wrapper that handles all complexity internally** - Simple API for enterprise testing.
## Philosophy
This package is a **wrapper** around Jest, not just a toolkit. It handles all Jest complexity internally, providing a simple API for downstream consumers. You shouldn't need to know about Jest hooks, setup functions, or internal details.
## Installation
```bash
npm install @kitiumai/jest-helpers
```
**Peer Dependencies:**
- `jest` ^29.0.0
- `typescript` ^5.0.0
- `@kitiumai/logger` ^2.0.0 (for console capture features)
## Quick Start
### Option 1: Auto-Setup (Simplest - Recommended)
```javascript
// jest.config.js
module.exports = {
setupFilesAfterEnv: ['@kitiumai/jest-helpers/auto-setup'],
};
```
Preset-specific drop-ins for clearer defaults:
```javascript
// jest.config.js
module.exports = {
// Choose the preset that matches your suite
setupFilesAfterEnv: ['@kitiumai/jest-helpers/auto-setup/integration'],
};
```
That's it! Everything is configured automatically. Use utilities in your tests:
```typescript
import { setupJest } from '@kitiumai/jest-helpers';
const test = setupJest('unit');
const mockFn = test.utils.mock.createMock({ returnValue: 'test' });
```
### Option 2: Manual Setup (More Control)
```typescript
import { setupJest } from '@kitiumai/jest-helpers';
const test = setupJest('unit');
// One-time setup (handles all complexity)
beforeAll(test.setup.beforeAll);
beforeEach(test.setup.beforeEach);
afterEach(test.setup.afterEach);
afterAll(test.setup.afterAll);
// Use utilities
const mockFn = test.utils.mock.createMock({ returnValue: 'test' });
```
## Features
- 🎯 **Simple API** - One function (`setupJest()`) to set up everything
- 🎭 **Mock Utilities** - Advanced mock creation and management
- 🧩 **Fixtures** - Lifecycle-managed test fixtures with automatic cleanup
- ✅ **Custom Matchers** - 10+ custom Jest matchers (auto-setup)
- 🗄️ **Database Testing** - Database seeding and verification
- 🌐 **HTTP Mocking** - Comprehensive API mocking utilities
- ⏱️ **Timer Management** - Fake timer utilities
- 📝 **Console Utilities** - Context-aware console capture with trace IDs
- 🔄 **Async Helpers** - Stream testing and async utilities
- 🏗️ **Builders** - Test data and mock builders
- 🔍 **Observability** - Log, metric, and trace assertions
- 🔒 **Strict Mode** - Enterprise-grade presets with best practices
## Main API: `setupJest()`
The recommended way to use this package is through the `setupJest()` wrapper function:
```typescript
import { setupJest } from '@kitiumai/jest-helpers';
// Create wrapper with preset
const test = setupJest('unit'); // or 'integration', 'e2e', 'contract'
// Setup Jest hooks (one-time)
beforeAll(test.setup.beforeAll);
beforeEach(test.setup.beforeEach);
afterEach(test.setup.afterEach);
afterAll(test.setup.afterAll);
// Use utilities
describe('My tests', () => {
it('should work', () => {
const mockFn = test.utils.mock.createMock({ returnValue: 'test' });
const db = test.fixture('database'); // If fixtures provided
const logs = test.console().getAll(); // Console capture
const requests = test.requests().getAll(); // Request recorder
});
});
```
### Presets
- **`'unit'`** - Fast, isolated tests (default)
- **`'integration'`** - HTTP mocking enabled, request recording
- **`'e2e'`** - Full stack, longer timeouts, all features
- **`'contract'`** - API contract validation, request recording
Each preset can be auto-registered via `@kitiumai/jest-helpers/auto-setup/<preset>` for copy/paste `jest.config.js` snippets.
### With Fixtures
```typescript
import { setupJest, createFixture, defineFixtures } from '@kitiumai/jest-helpers';
const fixtures = defineFixtures({
database: createFixture(
async () => setupDatabase(),
async (db) => db.close()
),
api: createFixture(
async () => setupApiClient(),
async (api) => api.disconnect()
),
});
const test = setupJest('integration', {
fixtures,
});
// Setup hooks
beforeAll(test.setup.beforeAll);
beforeEach(test.setup.beforeEach);
afterEach(test.setup.afterEach);
afterAll(test.setup.afterAll);
// Use fixtures in tests
describe('Integration tests', () => {
it('should work with database', () => {
const db = test.fixture('database');
// Use db - cleanup happens automatically
});
});
```
## Enterprise Features (Strict Mode)
### Context-Aware Console Capture
Automatically captures console output with trace ID propagation and sensitive data redaction:
```typescript
import { setupContextAwareConsole } from '@kitiumai/jest-helpers';
const hooks = setupContextAwareConsole({
autoLogToLogger: true,
redactSensitive: true,
sensitiveFields: ['password', 'token', 'apiKey'],
});
beforeEach(hooks.beforeEach);
afterEach(hooks.afterEach);
// Access captured logs with trace context
const capture = hooks.getCapture();
expect(capture).toHaveLogWithTraceId('trace-123');
```
### Automatic Fixture Cleanup
Playwright/Nest-style fixture management with guaranteed cleanup:
```typescript
import { createAutomaticFixtureHooks, createFixture } from '@kitiumai/jest-helpers';
const dbFixture = createFixture(
async () => await setupDatabase(),
async (db) => await db.close()
);
const hooks = createAutomaticFixtureHooks({
database: dbFixture,
});
beforeEach(hooks.beforeEach);
afterEach(hooks.afterEach);
// Access fixtures in tests
const db = hooks.getFixture<Database>('database');
```
### Observability Matchers
Assert on logs, metrics, and traces:
```typescript
import { setupObservabilityMatchers } from '@kitiumai/jest-helpers';
setupObservabilityMatchers();
// In your tests
expect(consoleCapture).toHaveLogContaining('User logged in', 'info');
expect(consoleCapture).toHaveLogWithTraceId('trace-123');
expect(consoleCapture).toHaveLogWithContext({ userId: 'user-123' });
expect(context).toHavePropagatedContext();
```
### Typed helpers and opinionated timers
- Use `defineFixtures` and `defineMocks` to infer keys/types without stringly-typed lookups.
- `test.withTimers()` enforces the preset timer policy (fake timers for unit; real timers elsewhere) while keeping cleanup automatic.
- `failOnTimerLeaks` and `failOnConsoleNoise` defaults guard against noisy or flaky tests; override through the `setupJest` options.
### HTTP/GraphQL happy path
`test.httpClient()` returns a facade that couples HTTP mocking, request recording, and contract assertions:
```typescript
const test = setupJest('integration');
const http = test.httpClient();
http.mock.mockGet('/users', { status: 200, data: [{ id: '1' }] });
const response = await http.request({ method: 'GET', url: '/users' });
expect(response.status).toBe(200);
expect(http.assertContract({ name: 'list-users', requests: http.requests() }).passed).toBe(true);
```
### Starter recipes
- **React component**: add `setupFilesAfterEnv: ['@kitiumai/jest-helpers/auto-setup/unit']`, wrap RTL tests with `test.withTimers()` when using fake timers, and declare component fixtures via `defineFixtures`.
- **Node service integration**: use `auto-setup/integration`, register API/database fixtures with `defineFixtures`, and rely on `test.httpClient()` to stub outbound HTTP.
- **Contract test**: enable `auto-setup/contract`, mock endpoints with `http.mock`, and export specs using `http.assertContract`.
### Wrapper-first guidance
Prefer the wrapper (`setupJest` or the `auto-setup` entrypoints) for consistent behavior across suites. Namespace exports remain for advanced/custom setups but lack the opinionated presets, guards, and redaction baked into the wrapper.
### GraphQL & Contract Testing
Mock GraphQL operations and record requests for contract testing:
```typescript
import {
getGraphQLMockRegistry,
createGraphQLFetchMock,
getRequestRecorder,
} from '@kitiumai/jest-helpers';
const registry = getGraphQLMockRegistry();
registry.mockQuery('GetUser', {
data: { user: { id: '1', name: 'John' } },
});
const mockFetch = createGraphQLFetchMock(registry);
global.fetch = mockFetch;
// Record requests for contract testing
const recorder = getRequestRecorder();
const contract = recorder.exportContract('user-service-v1');
recorder.assertContract(contract);
```
### Strict Mode Presets
Pre-configured strict mode with all best practices:
```typescript
import { StrictModePresets, LogLevel } from '@kitiumai/jest-helpers/strict-mode';
// Integration test preset
const hooks = StrictModePresets.integrationTest({
fixtures: {
database: dbFixture,
api: apiFixture,
},
logLevel: LogLevel.INFO,
});
beforeAll(hooks.beforeAll);
beforeEach(hooks.beforeEach);
afterEach(hooks.afterEach);
afterAll(hooks.afterAll);
// Access features
const db = hooks.getFixture<Database>('database');
const capture = hooks.getConsoleCapture();
const recorder = hooks.getRequestRecorder();
```
## Namespace Exports
Avoid naming conflicts with curated namespace exports:
```typescript
import { namespaced } from '@kitiumai/jest-helpers/namespaced';
// Organized imports
const { async, fixtures, http, matchers } = namespaced;
// Use without conflicts
fixtures.automatic.createAutomaticFixtureHooks({ ... });
http.graphql.getGraphQLMockRegistry();
matchers.observability.setupObservabilityMatchers();
```
## API Reference
### Mocks
#### `createMock<T>(options?)`
Create a Jest mock function with configuration.
**Parameters:**
- `options?: { returnValue?: unknown; returnValues?: unknown[]; implementation?: Function; rejectWith?: Error; resolveWith?: unknown }`
**Returns:** `MockFunction<T>`
**Example:**
```typescript
import { createMock } from '@kitiumai/jest-helpers';
const mockFn = createMock({
returnValue: 'default',
returnValues: ['first', 'second', 'third'],
});
const asyncMock = createMock({
resolveWith: { data: 'success' },
});
const errorMock = createMock({
rejectWith: new Error('Failed'),
});
```
#### `createMockObject<T>(template, mockOverrides?)`
Create a mock object with specified methods.
**Parameters:**
- `template: T` - Template object
- `mockOverrides?: Partial<Record<keyof T, MockSetupOptions>>`
**Returns:** `T`
**Example:**
```typescript
import { createMockObject } from '@kitiumai/jest-helpers';
const mockService = createMockObject(
{
getUser: (id: string) => Promise.resolve({ id, name: 'John' }),
updateUser: (id: string, data: any) => Promise.resolve(data),
},
{
getUser: { resolveWith: { id: '1', name: 'Mock User' } },
}
);
```
#### `createMockModule<T>(moduleName, mockImplementation)`
Create a mock module.
**Parameters:**
- `moduleName: string` - Module name to mock
- `mockImplementation: T` - Mock implementation
**Returns:** `T`
**Example:**
```typescript
import { createMockModule } from '@kitiumai/jest-helpers';
const mockApi = createMockModule('@api/client', {
fetch: jest.fn().mockResolvedValue({ data: 'test' }),
});
```
#### `ManagedMock<T>`
Mock with lifecycle management and call tracking.
**Example:**
```typescript
import { ManagedMock } from '@kitiumai/jest-helpers';
const mock = new ManagedMock({ returnValue: 'test' });
mock.fn('arg1', 'arg2');
const calls = mock.getCalls();
const lastCall = mock.getLastCall();
const count = mock.getCallCount();
const wasCalled = mock.wasCalledWith('arg1', 'arg2');
mock.clear(); // Clear call history
mock.reset(); // Reset mock
mock.restore(); // Restore original
```
### Fixtures
#### `createFixture<T>(setup, teardown?)`
Create a test fixture with setup and teardown.
**Parameters:**
- `setup: () => T | Promise<T>` - Setup function
- `teardown?: (data: T) => void | Promise<void>` - Teardown function
**Returns:** `Fixture<T>`
**Example:**
```typescript
import { createFixture } from '@kitiumai/jest-helpers';
const dbFixture = createFixture(
async () => {
const db = await connectToDatabase();
return db;
},
async (db) => {
await db.close();
}
);
// Use in test
const db = await dbFixture.setup();
// ... test code
await dbFixture.teardown(db);
```
#### `withFixture<T>(fixture, testFn)`
Run test with automatic fixture setup/teardown.
**Example:**
```typescript
import { withFixture } from '@kitiumai/jest-helpers';
await withFixture(userFixture, async (user) => {
expect(user.id).toBeDefined();
// Teardown automatically called
});
```
#### `FixtureManager`
Manage multiple fixtures.
**Example:**
```typescript
import { getGlobalFixtureManager } from '@kitiumai/jest-helpers';
const manager = getGlobalFixtureManager();
manager.register('user', userFixture);
manager.register('db', dbFixture);
const user = await manager.setup('user');
const db = await manager.setup('db');
await manager.teardownAll();
```
### Custom Matchers
#### Setup
```typescript
import { setupCustomMatchers } from '@kitiumai/jest-helpers';
beforeAll(() => setupCustomMatchers());
```
#### Available Matchers
- `toBeWithinRange(min, max)` - Number within range
- `toBeValidEmail()` - Valid email format
- `toBeValidUrl()` - Valid URL format
- `toContainObject(object)` - Array contains object
- `toHaveBeenCalledWithObject(object)` - Mock called with object
- `toMatchObject(expected)` - Object matches shape
**Example:**
```typescript
expect(5).toBeWithinRange(1, 10);
expect('user@example.com').toBeValidEmail();
expect('https://example.com').toBeValidUrl();
expect([{ id: 1 }, { id: 2 }]).toContainObject({ id: 1 });
expect(mockFn).toHaveBeenCalledWithObject({ userId: '123' });
```
### Console Utilities
#### `suppressConsole()`
Suppress all console output during tests.
**Returns:** `() => void` - Restore function
**Example:**
```typescript
import { suppressConsole } from '@kitiumai/jest-helpers';
beforeEach(() => {
const restore = suppressConsole();
// ... test code
restore(); // Restore console
});
```
#### `captureConsole()`
Capture console output for assertions.
**Returns:** `ConsoleCapture`
**Example:**
```typescript
import { captureConsole } from '@kitiumai/jest-helpers';
const capture = captureConsole();
console.log('Test message');
console.error('Error message');
expect(capture.hasOutput()).toBe(true);
expect(capture.errors).toHaveLength(1);
expect(capture.getByLevel('log')).toHaveLength(1);
```
#### `setupConsoleMocks(options)`
Setup console mocking in beforeEach/afterEach.
**Example:**
```typescript
import { setupConsoleMocks } from '@kitiumai/jest-helpers';
const consoleMocks = setupConsoleMocks({ suppress: true });
beforeEach(() => consoleMocks.beforeEach());
afterEach(() => consoleMocks.afterEach());
```
### Async Utilities
#### `consumeStream<T>(stream, options?)`
Consume async iterator and collect values.
**Parameters:**
- `stream: AsyncIterable<T>`
- `options?: { filter?: (value: T) => boolean; limit?: number; timeout?: number }`
**Returns:** `Promise<T[]>`
**Example:**
```typescript
import { consumeStream } from '@kitiumai/jest-helpers';
const stream = async function* () {
yield 1;
yield 2;
yield 3;
};
const values = await consumeStream(stream(), { limit: 2 });
// [1, 2]
```
#### `createMockStream<T>(values, options?)`
Create mock async generator.
**Example:**
```typescript
import { createMockStream } from '@kitiumai/jest-helpers';
const stream = createMockStream(['a', 'b', 'c'], {
delay: 100,
errorAfter: 2,
});
for await (const value of stream) {
console.log(value);
}
```
#### `safeCleanup(cleanupFn)`
Safe async cleanup with error handling.
**Example:**
```typescript
import { safeCleanup } from '@kitiumai/jest-helpers';
afterEach(async () => {
await safeCleanup(async () => {
await db.close();
});
});
```
#### `withCleanup<T>(setup, cleanup, testFn)`
Run test with automatic cleanup.
**Example:**
```typescript
import { withCleanup } from '@kitiumai/jest-helpers';
await withCleanup(
async () => await connectDB(),
async (db) => await db.close(),
async (db) => {
// Test code
}
);
```
### Builders
#### `createMockProvider<T>(methods)`
Create mock provider with methods.
**Example:**
```typescript
import { createMockProvider } from '@kitiumai/jest-helpers';
const provider = createMockProvider({
chat: jest.fn().mockResolvedValue({ content: 'Hello' }),
validate: jest.fn().mockReturnValue(true),
});
```
#### `createMockFetch(responses)`
Create mock fetch function.
**Example:**
```typescript
import { createMockFetch, createMockFetchResponse } from '@kitiumai/jest-helpers';
global.fetch = createMockFetch([
createMockFetchResponse({ data: 'test' }, { status: 200 }),
createMockFetchResponse({ error: 'Not found' }, { status: 404 }),
]);
```
#### `TestDataBuilder<T>`
Fluent builder for test data.
**Example:**
```typescript
import { createTestDataBuilder } from '@kitiumai/jest-helpers';
const builder = createTestDataBuilder({ id: '1', name: 'Default' })
.with('name', 'Custom Name')
.withMany({ age: 30, email: 'test@example.com' });
const data = builder.build();
```
### HTTP Mocking
#### `HttpMockRegistry`
HTTP mock handler registry.
**Example:**
```typescript
import { createHttpMockRegistry, ApiMocks } from '@kitiumai/jest-helpers';
const registry = createHttpMockRegistry();
registry.mockGet('/api/users', ApiMocks.success([{ id: 1 }]));
registry.mockPost('/api/users', ApiMocks.error('Validation failed', 400));
const handler = registry.getHandler({ method: 'GET', url: '/api/users' });
```
#### `ApiMocks`
Pre-built API response mocks.
**Example:**
```typescript
import { ApiMocks } from '@kitiumai/jest-helpers';
const success = ApiMocks.success({ data: 'test' });
const error = ApiMocks.error('Not found', 404);
const notFound = ApiMocks.notFound('User');
const paginated = ApiMocks.paginated([1, 2, 3], 1, 10, 100);
```
### Test Setup
#### `TestEnvironment`
Test environment setup and management.
**Example:**
```typescript
import { setupGlobalTestEnvironment, TestPresets } from '@kitiumai/jest-helpers';
const env = setupGlobalTestEnvironment(TestPresets.unitTest());
// ... tests
cleanupGlobalTestEnvironment();
```
#### `TestPresets`
Pre-configured test presets.
**Example:**
```typescript
import { TestPresets } from '@kitiumai/jest-helpers';
const unitOptions = TestPresets.unitTest();
const integrationOptions = TestPresets.integrationTest();
const dbOptions = TestPresets.databaseTest();
const apiOptions = TestPresets.apiTest();
```
### Timers
#### `TimerManager`
Fake timer management.
**Example:**
```typescript
import { getTimerManager } from '@kitiumai/jest-helpers';
const timers = getTimerManager();
timers.enableFakeTimers();
timers.advanceBy(1000);
timers.disableFakeTimers();
```
## Examples
### Complete Test Example (Simple API)
```typescript
import { setupJest, createFixture } from '@kitiumai/jest-helpers';
const test = setupJest('unit', {
fixtures: {
user: createFixture(
() => ({ id: '1', name: 'John', email: 'john@example.com' }),
(user) => console.log('Cleanup:', user.id)
),
},
});
// Setup hooks
beforeAll(test.setup.beforeAll);
beforeEach(test.setup.beforeEach);
afterEach(test.setup.afterEach);
afterAll(test.setup.afterAll);
describe('UserService', () => {
it('should create user', async () => {
const mockApi = test.utils.mock.createMock({
resolveWith: { success: true },
});
const user = test.fixture('user');
const result = await mockApi(user);
expect(result.success).toBe(true);
expect(mockApi).toHaveBeenCalledWith(user);
});
});
```
### Stream Testing
```typescript
import { consumeStream, createMockStream } from '@kitiumai/jest-helpers';
describe('Stream Processing', () => {
it('should consume async stream', async () => {
const stream = createMockStream(['a', 'b', 'c'], { delay: 10 });
const values = await consumeStream(stream, { limit: 2 });
expect(values).toEqual(['a', 'b']);
});
});
```
### HTTP Mocking
```typescript
import { createHttpMockRegistry, ApiMocks } from '@kitiumai/jest-helpers';
describe('API Client', () => {
it('should handle mocked responses', () => {
const registry = createHttpMockRegistry();
registry.mockGet('/api/users', ApiMocks.success([{ id: 1 }]));
const handler = registry.getHandler({
method: 'GET',
url: '/api/users',
});
expect(handler).toBeDefined();
});
});
```
## Configuration
All features integrate with the 5 internal packages:
- `@kitiumai/config` - Shared configuration
- `@kitiumai/lint` - Linting rules
- `@kitiumai/logger` - Structured logging with context (v2.0.0+)
- `@kitiumai/scripts` - Build scripts
- `@kitiumai/test-core` - Core utilities
## Best Practices
1. **Use `setupJest()` as the primary API** - Handles all complexity internally
2. **Use auto-setup for zero configuration** - Add to `jest.config.js` setupFilesAfterEnv
3. **Use strict mode for integration/E2E tests** - Prevents common mistakes
4. **Enable context propagation** - Essential for distributed tracing
5. **Use automatic fixtures** - Guarantees cleanup even on failures
6. **Record requests for contract testing** - Validates API contracts
7. **Use namespace exports** - Avoids naming conflicts when needed
## Migration Guide
### From Manual Setup to Simple API
**Before (Complex):**
```typescript
import {
setupCustomMatchers,
setupContextAwareConsole,
createAutomaticFixtureHooks,
} from '@kitiumai/jest-helpers';
beforeAll(() => setupCustomMatchers());
const consoleHooks = setupContextAwareConsole();
beforeEach(consoleHooks.beforeEach);
afterEach(consoleHooks.afterEach);
// ... more setup
```
**After (Simple):**
```typescript
import { setupJest } from '@kitiumai/jest-helpers';
const test = setupJest('unit');
beforeAll(test.setup.beforeAll);
beforeEach(test.setup.beforeEach);
afterEach(test.setup.afterEach);
afterAll(test.setup.afterAll);
```
### From Manual to Automatic Fixtures
**Before:**
```typescript
let db: Database;
beforeEach(async () => {
db = await setupDatabase();
});
afterEach(async () => {
await db.close();
});
```
**After:**
```typescript
import { setupJest, createFixture } from '@kitiumai/jest-helpers';
const test = setupJest('integration', {
fixtures: {
database: createFixture(setupDatabase, (db) => db.close()),
},
});
// Setup hooks once
beforeAll(test.setup.beforeAll);
beforeEach(test.setup.beforeEach);
afterEach(test.setup.afterEach);
afterAll(test.setup.afterAll);
// Use in tests
const db = test.fixture('database');
```
## TypeScript Support
Full TypeScript support with comprehensive type definitions.
```typescript
import type {
MockFunction,
MockSetupOptions,
Fixture,
ConsoleCapture,
JestWrapper,
JestPreset,
} from '@kitiumai/jest-helpers';
```
## License
MIT