@kitiumai/test-core
v3.0.0
Published
Core test utilities for enterprise testing - async helpers, builders, fixtures, mocks, and more
Maintainers
Keywords
Readme
@kitiumai/test-core
Core test utilities and shared functionality used across all test frameworks. Provides foundational utilities for data generation, configuration management, logging, and common async operations.
Public API surface
@kitiumai/test-core now exposes a single curated entry point that aligns with the docs/architecture-review.md guidance. Import from the root and tree-shake only what you need:
import {
createLogger,
createConfigManager,
waitFor,
DataGenerators,
Builder,
} from '@kitiumai/test-core';The public surface is snapshot-tested to guard against accidental breakage.
Installation
npm install @kitiumai/test-coreFeatures
- 🔄 Retry Logic - Retry operations with exponential backoff
- ⏱️ Async Utilities - Wait for conditions, sleep, deferred promises
- 📊 Data Generation - Comprehensive test data factories and generators
- ⚙️ Configuration - Environment-aware configuration management
- 📝 Logging - Structured logging with levels and context
- 🔧 Utilities - Deep clone, merge, sanitization helpers
Configuration with schema enforcement
The configuration manager validates inputs (including environment overrides) using a schema and returns an immutable snapshot suitable for global test setup.
import { createConfigManager } from '@kitiumai/test-core';
const config = createConfigManager({ baseUrl: 'https://example.com' });
const timeout = config.get('timeout');
const all = config.getAll(); // deeply frozen, safe to share globallyUsage & Tree-Shaking
Subpath Imports (Recommended for Bundle Size)
The test-core package provides modular subpath exports to help bundlers tree-shake unused utilities. Import only what you need:
// ✅ Minimal — only async helpers
import { retry, sleep, waitFor } from '@kitiumai/test-core/async';
// ✅ Data generation only — no loggers or fixtures
import { DataGenerators, Factories } from '@kitiumai/test-core/data';
// ✅ Configuration only — standalone setup
import { createConfigManager } from '@kitiumai/test-core/config';
// ✅ Fixtures only — for complex test setups
import { FixtureManager, createFixture } from '@kitiumai/test-core/fixtures';
// ✅ Logger utilities only
import { createLogger, expectLogs } from '@kitiumai/test-core/logger';
// ✅ Mocks and spies only
import { spyOn, createMockObject } from '@kitiumai/test-core/mocks';
// ✅ HTTP mocking
import { createHttpMockManager } from '@kitiumai/test-core/http';
// ✅ Timing utilities
import { measureTime, timeout, debounce } from '@kitiumai/test-core/timers';
// ✅ Builders
import { createBuilder, Sequence } from '@kitiumai/test-core/builders';Top-level Barrel (Works, But Larger)
If you import from the top-level barrel, modern bundlers will still tree-shake unused exports:
// ⚠️ Works but includes all utilities; bundler will tree-shake unused ones
import { retry, DataGenerators, createLogger } from '@kitiumai/test-core';Build Optimization Tips
- Use subpath imports in test suites to guarantee minimal bundle surface across all bundlers.
- For test utilities libraries, export subpath imports so consumers only pay for what they use.
- Verify bundling with esbuild:
esbuild --bundle --minify --analyze src/index.tsto see what's included.
The package provides ESM and CommonJS dual builds (dist/esm/ and dist/cjs/) with sideEffects: false, so tree-shaking works across all modern test runners and toolchains.
Quick Start
import { retry, waitUntil, sleep, DataGenerators, Factories } from '@kitiumai/test-core';
// Retry an operation
const result = await retry(() => fetch('/api/data'), { maxAttempts: 3, delayMs: 1000 });
// Wait for condition
await waitUntil(() => element.isVisible(), { timeoutMs: 5000 });
// Generate test data
const user = Factories.user({ email: '[email protected]' });
const email = DataGenerators.email();Logging with deterministic retrieval
The test logger stores structured entries in-memory and exposes deterministic retrieval helpers.
import { createLogger, expectLogs, LogLevel } from '@kitiumai/test-core';
const logger = createLogger(LogLevel.INFO, { userId: '123' });
logger.info('user login');
const loginLogs = expectLogs(logger, { level: LogLevel.INFO, contains: ['login'], minimum: 1 });Framework recipes
Opinionated setups for Jest, Vitest, and Playwright live in docs/recipes/frameworks.md.
API Reference
Utilities
retry<T>(fn, options?)
Retry a function with exponential backoff.
Parameters:
fn: () => Promise<T>- Function to retryoptions?: { maxAttempts?: number; delayMs?: number; backoffMultiplier?: number; onRetry?: (attempt: number, error: Error) => void }
Returns: Promise<T>
Example:
const data = await retry(
async () => {
const response = await fetch('/api/data');
if (!response.ok) throw new Error('Failed');
return response.json();
},
{
maxAttempts: 3,
delayMs: 1000,
backoffMultiplier: 2,
onRetry: (attempt, error) => {
console.log(`Attempt ${attempt} failed:`, error.message);
},
}
);waitUntil(condition, options?)
Wait for a condition to become true with polling.
Parameters:
condition: () => boolean | Promise<boolean>- Condition functionoptions?: { timeoutMs?: number; pollIntervalMs?: number; message?: string }
Returns: Promise<void>
Example:
await waitUntil(
async () => {
const response = await fetch('/api/status');
const data = await response.json();
return data.ready === true;
},
{
timeoutMs: 10000,
pollIntervalMs: 500,
message: 'Service not ready',
}
);sleep(ms)
Sleep for specified milliseconds.
Parameters:
ms: number- Milliseconds to sleep
Returns: Promise<void>
Example:
await sleep(1000); // Wait 1 seconddeepClone<T>(obj)
Deep clone an object.
Parameters:
obj: T- Object to clone
Returns: T
Example:
const original = { user: { name: 'John', age: 30 } };
const cloned = deepClone(original);
cloned.user.name = 'Jane'; // original is unchangeddeepMerge<T>(target, source)
Deep merge two objects.
Parameters:
target: T- Target objectsource: Partial<T>- Source object
Returns: T
Example:
const merged = deepMerge({ user: { name: 'John', age: 30 } }, { user: { age: 31 } });
// Result: { user: { name: 'John', age: 31 } }createDeferred<T>()
Create a deferred promise with external resolve/reject control.
Returns: { promise: Promise<T>; resolve: (value: T) => void; reject: (reason?: unknown) => void }
Example:
const { promise, resolve, reject } = createDeferred<string>();
setTimeout(() => resolve('Done!'), 1000);
const result = await promise; // 'Done!'sanitizeForLogging(data, sensitiveKeys?)
Sanitize data by redacting sensitive fields.
Parameters:
data: unknown- Data to sanitizesensitiveKeys?: string[]- Keys to redact (default: ['password', 'token', 'secret', 'apiKey', 'authorization'])
Returns: unknown
Example:
const sanitized = sanitizeForLogging({
username: 'john',
password: 'secret123',
email: '[email protected]',
});
// Result: { username: 'john', password: '***REDACTED***', email: '[email protected]' }Data Generation
DataGenerators
Comprehensive data generation utilities.
Available Generators:
string(length?, charset?)- Random stringnumber(min?, max?)- Random numberemail()- Random email addressuuid()- Random UUIDboolean()- Random booleandate(start?, end?)- Random datephoneNumber()- Random phone numberusername()- Random usernameurl()- Random URLarray<T>(generator, length?)- Array of generated valuesobject<T>(generators)- Object with generated valuesfirstName()- Random first namelastName()- Random last namefullName()- Random full namecompanyName()- Random company nameaddress()- Random street addresscity()- Random citycountry()- Random countryzipCode()- Random ZIP codeipAddress()- Random IP addresscreditCardNumber()- Masked credit card numberslug()- Random sluglocale()- Random localeisoTimestamp()- ISO timestamppastDate(daysAgo?)- Random past datefutureDate(daysFromNow?)- Random future dateenum<T>(values)- Random enum valueweighted<T>(options)- Weighted random choicehexColor()- Random hex colorjson<T>(depth?)- Random JSON object
Example:
import { DataGenerators } from '@kitiumai/test-core';
const email = DataGenerators.email(); // '[email protected]'
const uuid = DataGenerators.uuid(); // 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'
const user = DataGenerators.object({
name: () => DataGenerators.fullName(),
email: () => DataGenerators.email(),
age: () => DataGenerators.number(18, 65),
});createFactory<T>(defaultFactory)
Create a factory function for generating test data.
Parameters:
defaultFactory: (seed: number) => T- Factory function that uses seed
Returns: (overrides?: Partial<T>) => T
Example:
const userFactory = createFactory((seed) => ({
id: seed,
email: `user${seed}@example.com`,
name: `User ${seed}`,
}));
const user1 = userFactory(); // { id: 1, email: '[email protected]', name: 'User 1' }
const user2 = userFactory({ name: 'Custom Name' }); // { id: 2, email: '[email protected]', name: 'Custom Name' }createFactoryWithBuilder<T>(defaultFactory, relations?)
Create a factory with relationship builders.
Parameters:
defaultFactory: (seed: number) => T- Factory functionrelations?: Record<string, (seed: number) => unknown>- Relationship generators
Returns: (overrides?: Partial<T> & Record<string, unknown>) => T
Example:
const postFactory = createFactoryWithBuilder((seed) => ({ id: seed, title: `Post ${seed}` }), {
author: (seed) => ({ id: seed, name: `Author ${seed}` }),
});
const post = postFactory({ title: 'Custom Title' });
// { id: 1, title: 'Custom Title', author: { id: 1, name: 'Author 1' } }Factories
Pre-built factory functions for common entities.
Available Factories:
Factories.user(overrides?)- User factoryFactories.post(overrides?)- Post factoryFactories.comment(overrides?)- Comment factoryFactories.apiResponse(overrides?)- API response factoryFactories.company(overrides?)- Company factoryFactories.product(overrides?)- Product factoryFactories.order(overrides?)- Order factoryFactories.todo(overrides?)- Todo factoryFactories.article(overrides?)- Article factoryFactories.profile(overrides?)- Profile factory
Example:
import { Factories } from '@kitiumai/test-core';
const user = Factories.user({ email: '[email protected]' });
const post = Factories.post({ authorId: user.id });Configuration
createConfigManager(initialConfig?)
Create a configuration manager instance.
Parameters:
initialConfig?: TestConfig- Initial configuration
Returns: ConfigManager
Example:
import { createConfigManager } from '@kitiumai/test-core';
const config = createConfigManager({
timeout: 30000,
retries: 2,
verbose: true,
});
config.set('baseUrl', 'http://localhost:3000');
const timeout = config.get('timeout');getConfigManager()
Get the global configuration manager instance.
Returns: ConfigManager
Example:
import { getConfigManager } from '@kitiumai/test-core';
const config = getConfigManager();
const baseUrl = config.get('baseUrl');Logging
createLogger(level?, context?)
Create a logger instance.
Parameters:
level?: LogLevel- Log level (DEBUG, INFO, WARN, ERROR)context?: LogContext- Initial context
Returns: Logger
Example:
import { createLogger, LogLevel } from '@kitiumai/test-core';
const logger = createLogger(LogLevel.DEBUG, { testId: 'test-123' });
logger.info('Test started', { userId: 'user-456' });
logger.error('Test failed', new Error('Something went wrong'), { step: 'login' });
const logs = logger.getLogs(LogLevel.ERROR);
logger.clear();Examples
Retry with Backoff
import { retry } from '@kitiumai/test-core';
const fetchWithRetry = async (url: string) => {
return await retry(
async () => {
const response = await fetch(url);
if (!response.ok) throw new Error(`HTTP ${response.status}`);
return response.json();
},
{
maxAttempts: 5,
delayMs: 1000,
backoffMultiplier: 2,
onRetry: (attempt, error) => {
console.log(`Retry ${attempt}: ${error.message}`);
},
}
);
};Wait for Async Condition
import { waitUntil } from '@kitiumai/test-core';
// Wait for API to be ready
await waitUntil(
async () => {
try {
const response = await fetch('/api/health');
return response.ok;
} catch {
return false;
}
},
{
timeoutMs: 30000,
pollIntervalMs: 1000,
message: 'API not ready',
}
);Generate Test Data
import { DataGenerators, Factories } from '@kitiumai/test-core';
// Generate single values
const email = DataGenerators.email();
const phone = DataGenerators.phoneNumber();
const address = DataGenerators.address();
// Generate complex objects
const users = DataGenerators.array(() => Factories.user(), 10);
// Generate with relationships
const posts = DataGenerators.array(() => Factories.post({ authorId: DataGenerators.uuid() }), 5);Configuration Management
import { getConfigManager } from '@kitiumai/test-core';
const config = getConfigManager();
// Set configuration
config.set('baseUrl', process.env.BASE_URL || 'http://localhost:3000');
config.set('timeout', 30000);
// Get configuration
const baseUrl = config.get('baseUrl');
const timeout = config.get('timeout');
// Merge configuration
config.merge({ timeout: 60000, retries: 3 });
// Get all configuration
const allConfig = config.getAll();Structured Logging
import { createLogger, LogLevel } from '@kitiumai/test-core';
const logger = createLogger(LogLevel.INFO, {
testSuite: 'user-service',
environment: 'test',
});
logger.debug('Debug message', { userId: '123' });
logger.info('Test started', { testName: 'login-test' });
logger.warn('Deprecated API used', { endpoint: '/api/v1/users' });
logger.error('Test failed', new Error('Connection timeout'), {
endpoint: '/api/users',
attempt: 3,
});
// Get logs by level
const errorLogs = logger.getLogs(LogLevel.ERROR);TypeScript Support
Full TypeScript support with comprehensive type definitions.
import type { TestConfig, LogLevel, LogContext } from '@kitiumai/test-core';License
MIT
