npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@kitiumai/test-core

v3.0.0

Published

Core test utilities for enterprise testing - async helpers, builders, fixtures, mocks, and more

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-core

Features

  • 🔄 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 globally

Usage & 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

  1. Use subpath imports in test suites to guarantee minimal bundle surface across all bundlers.
  2. For test utilities libraries, export subpath imports so consumers only pay for what they use.
  3. Verify bundling with esbuild: esbuild --bundle --minify --analyze src/index.ts to 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 retry
  • options?: { 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 function
  • options?: { 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 second

deepClone<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 unchanged

deepMerge<T>(target, source)

Deep merge two objects.

Parameters:

  • target: T - Target object
  • source: 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 sanitize
  • sensitiveKeys?: 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 string
  • number(min?, max?) - Random number
  • email() - Random email address
  • uuid() - Random UUID
  • boolean() - Random boolean
  • date(start?, end?) - Random date
  • phoneNumber() - Random phone number
  • username() - Random username
  • url() - Random URL
  • array<T>(generator, length?) - Array of generated values
  • object<T>(generators) - Object with generated values
  • firstName() - Random first name
  • lastName() - Random last name
  • fullName() - Random full name
  • companyName() - Random company name
  • address() - Random street address
  • city() - Random city
  • country() - Random country
  • zipCode() - Random ZIP code
  • ipAddress() - Random IP address
  • creditCardNumber() - Masked credit card number
  • slug() - Random slug
  • locale() - Random locale
  • isoTimestamp() - ISO timestamp
  • pastDate(daysAgo?) - Random past date
  • futureDate(daysFromNow?) - Random future date
  • enum<T>(values) - Random enum value
  • weighted<T>(options) - Weighted random choice
  • hexColor() - Random hex color
  • json<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 function
  • relations?: 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 factory
  • Factories.post(overrides?) - Post factory
  • Factories.comment(overrides?) - Comment factory
  • Factories.apiResponse(overrides?) - API response factory
  • Factories.company(overrides?) - Company factory
  • Factories.product(overrides?) - Product factory
  • Factories.order(overrides?) - Order factory
  • Factories.todo(overrides?) - Todo factory
  • Factories.article(overrides?) - Article factory
  • Factories.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