New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

@corez/mock

Package Overview
Dependencies
Maintainers
0
Versions
24
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@corez/mock - npm Package Compare versions

Comparing version 0.2.2 to 0.2.4

src/constants.ts

1018

dist/cjs/index.d.ts
/**
* Configuration management for the mocking system
*/
/**
* Global configuration options
*/
interface Config {
/** Whether to enable debug mode */
debug: boolean;
/** Whether to track method calls by default */
trackCalls: boolean;
/** Whether to allow undefined properties */
allowUndefined: boolean;
/** Whether to use strict mode */
strict: boolean;
/** Default timeout for async operations (ms) */
timeout: number;
/** Whether to preserve prototype chain */
preservePrototype: boolean;
}
/**
* Configuration update type
*/
type ConfigUpdate = Partial<Config>;
/**
* Configuration statistics
*/
interface ConfigStats {
updateCount: number;
lastUpdate: number;
cacheHits: number;
}
/**
* Global configuration API
* Similar to Jest's global config API
*/
declare const configManager: {
/**
* Gets current configuration
*/
get(): Readonly<Config>;
/**
* Updates configuration
*/
set(options: ConfigUpdate): void;
/**
* Resets configuration to defaults
*/
reset(): void;
/**
* Creates a scoped configuration
*/
with<T>(options: ConfigUpdate, fn: () => T): T;
/**
* Gets configuration statistics
*/
getStats(): ConfigStats;
};
declare const mockConfig: {
/**
* Gets current configuration
*/
get(): Readonly<Config>;
/**
* Updates configuration
*/
set(options: ConfigUpdate): void;
/**
* Resets configuration to defaults
*/
reset(): void;
/**
* Creates a scoped configuration
*/
with<T>(options: ConfigUpdate, fn: () => T): T;
/**
* Gets configuration statistics
*/
getStats(): ConfigStats;
};
/**
* Custom error types and error handling utilities for the mocking system
*/
/**
* Error codes for different types of mocking errors
*/
declare enum MockErrorCode {
CIRCULAR_REFERENCE = "CIRCULAR_REF",
INVALID_TARGET = "INVALID_TARGET",
INITIALIZATION_FAILED = "INIT_FAILED",
TYPE_MISMATCH = "TYPE_MISMATCH",
INVALID_IMPLEMENTATION = "INVALID_IMPL",
INVALID_CONFIGURATION = "INVALID_CONFIG",
VERIFICATION_ERROR = "VERIFY_ERROR",
EXPECTATION_FAILED = "EXPECT_FAILED",
PERFORMANCE_CONSTRAINT_VIOLATED = "PERF_VIOLATED"
}
/**
* Base error class for all mocking related errors
*/
declare class MockError extends Error {
readonly code: MockErrorCode;
readonly details?: Record<string, any> | undefined;
constructor(code: MockErrorCode, message: string, details?: Record<string, any> | undefined);
/**
* Creates a circular reference error
*/
static circularReference(property: string | symbol, details?: Record<string, any>): MockError;
/**
* Creates an invalid target error
*/
static invalidTarget(target: any, expected: string): MockError;
/**
* Creates an initialization failed error
*/
static initializationFailed(message: string, details?: Record<string, any>): MockError;
/**
* Creates a type mismatch error
*/
static typeMismatch(expected: string, received: string, details?: Record<string, any>): MockError;
/**
* Creates an invalid implementation error
*/
static invalidImplementation(message: string, details?: Record<string, any>): MockError;
/**
* Creates an expectation failed error
*/
static expectationFailed(message: string, details?: Record<string, any>): MockError;
/**
* Creates a performance constraint violation error
*/
static performanceConstraintViolated(message: string, details?: Record<string, any>): MockError;
}
/**
* Cache management types and utilities for the mocking system
*/
/**
* Represents a cached value with its metadata
*/
interface CacheEntry<T = any> {
value: T;
descriptor?: PropertyDescriptor;
ref?: WeakRef<any>;
timestamp: number;
}
/**
* Cache manager for mock instances
*/
declare class MockCache {
private cache;
private registry;
/**
* Set a value in the cache with optional descriptor
*/
set(key: string | symbol, value: any, descriptor?: PropertyDescriptor): void;
/**
* Get a value from the cache
*/
get(key: string | symbol): any | undefined;
/**
* Get a property descriptor from the cache
*/
getDescriptor(key: string | symbol): PropertyDescriptor | undefined;
/**
* Check if a key exists in the cache
*/
has(key: string | symbol): boolean;
/**
* Delete a key from the cache
*/
delete(key: string | symbol): void;
/**
* Clear all entries from the cache
*/
clear(): void;
/**
* Get all keys in the cache
*/
keys(): IterableIterator<string | symbol>;
}
/**
* Basic function type
*/
type Fn = (...args: any[]) => any;
/**
* Async function type
*/
type AsyncFn = (...args: any[]) => Promise<any>;
/**
* Class constructor type
*/
type Constructor<T> = new (...args: any[]) => T;
/**
* Makes all properties optional recursively
*/
type DeepPartial<T> = {
[P in keyof T]?: T[P] extends (infer U)[] ? DeepPartial<U>[] : T[P] extends object ? DeepPartial<T[P]> : T[P];
};
/**
* Recursive partial type with function handling
*/
type RecursivePartial<T> = {
[K in keyof T]?: T[K] extends Fn ? (...args: Parameters<T[K]>) => RecursivePartial<ReturnType<T[K]>> | ReturnType<T[K]> : T[K] extends Array<any> ? Array<RecursivePartial<T[K][number]>> : T[K] extends object ? RecursivePartial<T[K]> : T[K];
};
/**
* Base configuration type for mocks
*/
interface BaseConfig<T> {
returns?: T;
resolves?: Awaited<T>;
rejects?: any;
throws?: any;
delay?: number;
implementation?: (...args: any[]) => T;
}
/**
* Mock call information
*/
type MockCall<T extends any[]> = {
args: T;
returnValue: any;
error?: any;
timestamp: number;
};
/**
* Mock behavior configuration
*/
type MockBehaviorConfig<T> = BaseConfig<T>;
/**
* Mock behavior store
*/
type MockBehavior$1<T> = MockBehaviorConfig<T> & {
callSpecific?: Map<number, MockBehaviorConfig<T>>;
};
/**
* Property descriptor type
*/
type PropertyDescriptor$1<T> = {
get?: () => T;
set?: (value: T) => void;
value?: T;
configurable?: boolean;
enumerable?: boolean;
};
/**
* Base matcher type
*/
interface Matcher<T = any> {
(value: T): boolean;
__isMatcher: true;
description: string;
}
/**
* Mock matcher factory
*/
type MatcherFactory = {
any(): Matcher;
string(): Matcher<string>;
number(): Matcher<number>;
boolean(): Matcher<boolean>;
array(): Matcher<any[]>;
object(): Matcher<object>;
func(): Matcher<Fn>;
contains(value: any): Matcher;
matches(regex: RegExp): Matcher<string>;
custom<T>(predicate: (value: T) => boolean, description: string): Matcher<T>;
};

@@ -282,35 +16,8 @@ /**

/**
* Base mock function interface
* Mock function type with Jest-like chaining methods
*/
interface BaseMockFunction<T extends Fn = Fn> extends Function {
interface MockFunction<T extends Fn = Fn> extends Function {
(...args: Parameters<T>): ReturnType<T>;
calls: {
all(): Array<{
args: Parameters<T>;
}>;
count(): number;
};
_isMockFunction?: boolean;
_isMockFunction: boolean;
_this?: any;
}
/**
* Mock function type with Jest-like chaining methods
*/
interface MockFunction<T extends Fn = Fn> extends BaseMockFunction<T> {
mockReturnValue(value: ReturnType<T>): this;
mockResolvedValue<U>(value: U): MockFunction<AsyncFn & {
(...args: Parameters<T>): Promise<U>;
}>;
mockImplementation(fn: T): this;
mockReset(): void;
mockClear(): void;
and: MockBehavior<T>;
toHaveBeenCalled(): boolean;
toHaveBeenCalledTimes(n: number): boolean;
toHaveBeenCalledWith(...args: Parameters<T>): boolean;
}
/**
* Jest mock type
*/
interface JestMock<T extends Fn = Fn> extends BaseMockFunction<T> {
mock: {

@@ -322,701 +29,48 @@ calls: Parameters<T>[];

}>;
instances: any[];
contexts: any[];
lastCall?: Parameters<T>;
};
mockReturnValue(value: ReturnType<T>): this;
mockResolvedValue<U>(value: U): this;
mockImplementation(fn: T): this;
mockReset(): void;
mockClear(): void;
}
/**
* Jasmine spy type
*/
interface JasmineSpy<T extends Fn = Fn> extends BaseMockFunction<T> {
calls: {
all: () => Array<{
args: Parameters<T>;
}>;
count: () => number;
};
mockImplementation: (fn: T) => MockFunction<T>;
mockReturnValue: (value: ReturnType<T>) => MockFunction<T>;
mockResolvedValue: <U>(value: U) => MockFunction<T>;
mockRejectedValue: <U>(value: U) => MockFunction<T>;
mockReset: () => void;
mockClear: () => void;
mockRestore: () => void;
getMockImplementation: () => T | undefined;
getMockName: () => string;
and: MockBehavior<T>;
toHaveBeenCalled: () => boolean;
toHaveBeenCalledTimes: (n: number) => boolean;
toHaveBeenCalledWith: (...args: Parameters<T>) => boolean;
mockName: (name: string) => MockFunction<T>;
mockReturnThis: () => MockFunction<T>;
mockResolvedValueOnce: <U>(value: U) => MockFunction<T>;
mockRejectedValueOnce: <U>(value: U) => MockFunction<T>;
mockReturnValueOnce: (value: ReturnType<T>) => MockFunction<T>;
mockImplementationOnce: (fn: T) => MockFunction<T>;
}
/**
* Mock adapter capabilities
*/
interface MockCapabilities {
createSpy<T extends Fn = Fn>(): MockFunction<T>;
spyOn<T extends object>(obj: T, key: keyof T): void;
}
/**
* Base mock adapter interface
*/
interface BaseAdapter extends MockCapabilities {
getSpy(property: string): any;
}
/**
* Mock adapter interface
*/
interface Adapter extends BaseAdapter {
replaceFn<T extends object>(obj: T, key: keyof T, fn: Fn): void;
createPropertySpy<T>(): {
get?: MockFunction;
set?: MockFunction;
};
}
/**
* Extended with interface
*/
interface ExtendedWith<T> {
with(stubs: RecursivePartial<T>): T;
}
/**
* Mock call specific behavior
*/
type MockCallBehavior<T extends Fn> = BaseConfig<ReturnType<T>> & {
returns: (value: ReturnType<T>) => MockFunction<T>;
resolves: (value: Awaited<ReturnType<T>>) => MockFunction<T>;
rejects: (error: any) => MockFunction<T>;
throws: (error: any) => MockFunction<T>;
implementation: (fn: T) => MockFunction<T>;
};
/**
* Base mock interface
*/
interface BaseMock<T> {
__isMock: boolean;
reset(): void;
restore?(): void;
with(stubs: RecursivePartial<T>): T;
}
/**
* Deep mock type for objects
*/
type DeepMock<T> = {
[K in keyof T]: T[K] extends Fn ? MockFunction<T[K]> : T[K] extends object ? DeepMock<T[K]> : T[K];
} & BaseMock<T> & {
defineProperty: <K extends keyof T>(prop: K, descriptor: PropertyDescriptor$1<T[K]>) => void;
};
/**
* Mock configuration options
*/
interface MockOptions {
modifyOriginal?: boolean;
mockPrototype?: boolean;
mockStatic?: boolean;
preserveConstructor?: boolean;
selective?: boolean;
preserveThis?: boolean;
autoSpy?: boolean;
handleCircular?: boolean;
}
/**
* Mock class options
*/
interface MockClassOptions extends Pick<MockOptions, 'modifyOriginal' | 'mockPrototype' | 'mockStatic' | 'preserveConstructor'> {
}
/**
* Mock object options
*/
interface MockObjectOptions extends Pick<MockOptions, 'modifyOriginal' | 'mockPrototype'> {
}
/**
* Mock result interface
*/
interface MockResult {
restore?(): void;
reset(): void;
}
/**
* Mock class result
*/
interface MockClassResult<T> extends MockResult {
Class: Constructor<T>;
}
/**
* Mock object result
*/
interface MockObjectResult<T> extends MockResult {
object: T;
}
/**
* Spy adapter type
*/
interface SpyAdapter extends MockCapabilities {
getSpy(property: string): any;
spyAndCallFake<T extends object, K extends keyof T>(object: T, key: K, stub: T[K] & Function): void;
spyAndCallThrough<T extends object, K extends keyof T>(object: T, key: K): void;
}
/**
* Supported mock frameworks
*/
type Framework = 'jasmine' | 'jest' | 'none';
/**
* Makes all methods in T into MockFunction types
*/
type MockOf<T> = {
[P in keyof T]: T[P] extends Fn ? MockFunction<T[P]> : T[P] extends object ? MockOf<T[P]> : T[P];
};
/**
* Makes all static members of a class into MockFunction types
*/
type StaticMockOf<T> = {
[P in keyof T]: T[P] extends Fn ? MockFunction<T[P]> : T[P];
};
/**
* Represents a mocked class constructor
*/
type ClsMock<T extends new (...args: any[]) => any> = StaticMockOf<T> & {
new (...args: ConstructorParameters<T>): MockOf<InstanceType<T>>;
prototype: MockOf<InstanceType<T>>;
};
/**
* Options for configuring class mocks
*/
interface ClsMockOptions<T extends new (...args: any[]) => any> extends Pick<MockOptions, 'selective'> {
implementation?: DeepPartial<InstanceType<T>>;
}
/**
* Partial mock options
*/
interface PartialOptions<T> extends Pick<MockOptions, 'selective' | 'preserveThis' | 'autoSpy' | 'handleCircular'> {
}
/**
* Partial mock builder interface
*/
interface PartialBuilder<T extends object> {
with(stubs?: DeepPartial<T>, options?: PartialOptions<T>): T;
spy(...methods: (keyof T)[]): this;
preserve(...properties: (keyof T)[]): this;
}
/**
* Universal mock function that provides a unified entry point for mocking objects and classes.
* It automatically detects the input type and applies the appropriate mocking strategy.
*
* @template T -The type to mock
* @param target -The target to mock (class constructor, object instance, or partial implementation)
* @param options -Optional configuration for mocking behavior
* @returns A mocked version of the target
* @throws {TypeError} When the target type cannot be determined
* @throws {Error} When circular references are detected without proper handling
*
* @remarks
* This function serves as the main entry point for all mocking operations.
* It intelligently determines the type of input and applies the most appropriate mocking strategy:
* -Class mocking: Creates a mock class with spied methods
* -Object mocking: Creates a proxy-based mock with tracked properties
* -Partial mocking: Allows selective method/property mocking
* -Interface mocking: Creates complete mock from type information
*
* @example
* ```typescript
* // Mock a class
* class Service {
* getData() { return 'data'; }
* }
* const MockService = mock(Service);
* const instance = new MockService();
*
* // Mock a class with options
* const MockService = mock(Service, {
* selective: true,
* implementation: {
* getData: () => 'mocked'
* }
* });
*
* // Mock an object
* interface User {
* id: number;
* getName(): string;
* }
* const mockUser = mock<User>({
* id: 1,
* getName: () => 'John'
* });
*
* // Create a complete mock from interface
* const mockUser = mock<User>();
*
* // Partial mock of existing instance
* const realService = new Service();
* const mockService = mock(realService, {
* getData: () => 'mocked'
* });
* ```
*
* @see {@link mock.object} For creating complete mock objects
* @see {@link mock.partial} For creating partial mocks
* @see {@link mock.cls} For creating mock classes
* @see {@link mock.of} For creating mocks from stubs
*/
declare function mock<T extends new (...args: any[]) => any>(target: T, options?: ClsMockOptions<T>): ClsMock<T>;
declare function mock<T extends object>(target: T, implementation?: DeepPartial<T>): T;
declare function mock<T extends object>(partialImpl?: DeepPartial<T>): MockOf<T>;
/**
* Mock utilities namespace that provides a comprehensive set of mocking tools
* for testing TypeScript/JavaScript applications.
*
* @namespace mock
* @description
* A comprehensive mocking framework that provides tools for:
* -Function mocking with spy capabilities
* -Object mocking with property tracking
* -Class mocking with inheritance support
* -Partial mocking for selective member overrides
* -Framework compatibility (Jest/Jasmine)
*
* The namespace provides a unified API for all mocking operations while maintaining
* type safety and providing detailed tracking and verification capabilities.
*/
declare namespace mock {
/**
* Creates a spy function that can track calls and mock implementations.
* The spy function maintains the same type signature as the original function.
*
* @template T -The function type to mock
* @returns {MockFunction<T>} A mock function with spy capabilities
*
* @remarks
* Spy functions provide comprehensive tracking and control over function calls:
* -Call count tracking
* -Arguments validation
* -Return value mocking
* -Implementation replacement
* -Asynchronous behavior simulation
*
* The spy maintains the original function's type information while adding
* additional methods for tracking and controlling behavior.
*
* @example
* ```typescript
* // Create a typed spy
* const mockFn = mock.fn<(x: number) => string>();
*
* // Configure behavior
* mockFn.mockReturnValue('mocked');
* mockFn.mockImplementation(x => x.toString());
*
* // Use in tests
* mockFn(42);
* expect(mockFn).toHaveBeenCalledWith(42);
* expect(mockFn.calls.count()).toBe(1);
* ```
*
* @see {@link MockFunction} For the complete spy API
*/
function fn<T extends Fn = Fn>(): MockFunction<T>;
/**
* Creates a fully mocked object where all properties and methods are spies.
* The mock object maintains the same type information as the original.
*
* @template T -The object type to mock
* @returns {T} A mock object with all members as spies
*
* @remarks
* Object mocking provides comprehensive control over object behavior:
* -Automatic spy creation for methods
* -Property value tracking
* -Getter/setter interception
* -Nested object handling
* -Circular reference protection
*
* The mock maintains the original object's type information while adding
* spy capabilities to all methods and tracking for all properties.
*
* Features:
* -Auto-creates spies for all methods
* -Supports getters and setters
* -Maintains property descriptors
* -Handles nested objects
* -Preserves type safety
*
* @example
* ```typescript
* interface Service {
* getData(): Promise<string>;
* value: number;
* }
*
* const mockService = mock.object<Service>();
* mockService.getData.mockResolvedValue('data');
* mockService.value = 42;
*
* await mockService.getData();
* expect(mockService.getData).toHaveBeenCalled();
* ```
*
* @see {@link mock.partial} For creating partial mocks
* @see {@link mock.of} For creating mocks from stubs
*/
function object<T extends object>(): T;
/**
* Creates a partial mock from an existing object, allowing selective mocking of members.
* This is useful when you want to mock only specific parts of an object while keeping
* the rest of the functionality intact.
*
* @template T -The object type to partially mock
* @param base -The original object to create a partial mock from
* @param stubs -Optional stubs to initialize the mock with
* @param options -Configuration options for the partial mock
* @returns {T | PartialBuilder<T>} A mock object with partial mocking or a builder for further configuration
*
* @remarks
* Partial mocking provides fine-grained control over which members to mock:
* -Selective method mocking
* -Original behavior preservation
* -Prototype chain maintenance
* -Nested object support
* -Circular reference handling
*
* Features:
* -Selective member mocking
* -Preserves original behavior
* -Maintains prototype chain
* -Supports nested objects
* -Type-safe implementation
*
* @example
* ```typescript
* class Service {
* getData() { return 'real data'; }
* process() { return this.getData().toUpperCase(); }
* }
*
* // Method 1: Using with()
* const mockService1 = mock.partial(new Service()).with({
* getData: () => 'mock data'
* });
*
* // Method 2: Direct two parameters
* const mockService2 = mock.partial(new Service(), {
* getData: () => 'mock data'
* });
*
* expect(mockService1.process()).toBe('MOCK DATA');
* expect(mockService2.process()).toBe('MOCK DATA');
* ```
*
* @throws {Error} When circular references are detected without proper handling
* @see {@link PartialBuilder} For the builder pattern API
* @see {@link PartialOptions} For available configuration options
*/
function partial<T extends object>(base: T, stubs: DeepPartial<T>, options?: PartialOptions<T>): T;
function partial<T extends object>(base: T): PartialBuilder<T>;
/**
* Casts a partial object to its full type, useful for type coercion in mocks
* @template T -The target type to cast to
* @param partial -The partial object to cast
* @returns The partial object cast to type T
* @example
* ```typescript
* interface Complex {
* nested: { value: number };
* }
* const partial = { nested: { value: 42 } };
* const typed = mock.cast<Complex>(partial);
* ```
*/
function cast<T extends object>(partial: DeepPartial<T>): T;
/**
* Creates a mock from stubs, supporting both object and array types.
* Preserves provided values while automatically mocking undefined members.
*
* @template T -The type to create a mock from
* @param stubs -Optional stubs to initialize the mock with
* @returns {MockOf<T>} A mock object or array with all methods as MockFunction
*
* @remarks
* Mock creation from stubs provides flexible initialization:
* -Value preservation
* -Automatic method mocking
* -Array support
* -Nested object handling
* -Type inference
*
* Features:
* -Preserves provided property values
* -Preserves and monitors provided method implementations
* -Automatically mocks undefined methods
* -Handles nested objects and arrays
* -Maintains type safety
*
* @example
* ```typescript
* interface User {
* id: number;
* name: string;
* getData(): string;
* }
*
* // With initial values
* const mockUser = mock.of<User>({
* id: 1,
* name: 'John'
* });
* // id and name are preserved
* // getData is automatically mocked
* mockUser.getData.mockReturnValue('data');
*
* // With method implementation
* const mockWithMethod = mock.of<User>({
* id: 1,
* getData: () => 'data'
* });
* // getData implementation is preserved but monitored
* mockWithMethod.getData.mockReturnValue('new data');
* ```
*
* @throws {Error} When circular references are detected
* @see {@link MockOf} For the resulting mock type
*/
function of<T extends Array<any>>(stubs?: Array<DeepPartial<T[number]>>): MockOf<T>;
function of<T extends ReadonlyArray<any>>(stubs?: ReadonlyArray<DeepPartial<T[number]>>): MockOf<T>;
function of<T extends object>(stubs?: DeepPartial<T>): MockOf<T>;
/**
* Replaces a method with mock implementation while preserving original properties
* @template T -The object type containing the method
* @template K -The key of the method to replace
* @param obj -The object containing the method
* @param key -The key of the method to replace
* @param impl -The mock implementation
* @example
* ```typescript
* const obj = { method: () => 'original' };
* mock.replace(obj, 'method', () => 'mocked');
* ```
*/
function replace<T extends object, K extends keyof T>(obj: T, key: K, impl: Fn): void;
/**
* Restores replaced methods to their original implementations
* @template T -The object type containing the method
* @template K -The key of the method to restore
* @param obj -The object containing the method
* @param key -Optional key of the specific method to restore
* @returns {boolean} Whether any methods were restored
* @example
* ```typescript
* // Restore specific method
* mock.restore(service, 'method');
*
* // Restore all replaced methods
* mock.restore(service);
* ```
*/
function restore<T extends object>(obj: T): boolean;
function restore<T extends object, K extends keyof T>(obj: T, key: K): boolean;
/**
* Gets a list of currently replaced methods on an object
* @template T -The object type to check
* @param obj -The object to check
* @returns {string[]} Array of replaced method names
*/
function getReplacedMethods<T extends object>(obj: T): string[];
/**
* Verifies if a method has been replaced
* @template T -The object type containing the method
* @template K -The key of the method to check
* @param obj -The object containing the method
* @param key -The key of the method to check
* @returns {boolean} Whether the method is currently replaced
*/
function isReplaced<T extends object, K extends keyof T>(obj: T, key: K): boolean;
/**
* Verifies if a restored method matches its original implementation
* @template T -The object type containing the method
* @template K -The key of the method to verify
* @param obj -The object containing the method
* @param key -The key of the method to verify
* @returns {boolean} Whether the current implementation matches the original
*/
function verifyRestored<T extends object, K extends keyof T>(obj: T, key: K): boolean;
/**
* Creates a mock class that maintains the original class's type information and behavior.
* Supports both full monitoring and selective method mocking.
*
* @template T -The class type to mock
* @param originalClass -The original class to create a mock from
* @param options -Configuration options for class mocking
* @returns {ClsMock<T>} A mock class with the same interface as the original
*
* @remarks
* Class mocking provides comprehensive control over class behavior:
* -Constructor interception
* -Method spying
* -Property tracking
* -Static member handling
* -Inheritance support
*
* Features:
* -Maintains original class structure
* -Supports constructor arguments
* -Handles static members
* -Preserves inheritance chain
* -Allows selective mocking
*
* @example
* ```typescript
* // Monitor all methods
* const MockService = mock.cls(DataService);
*
* // Selective monitoring with custom implementation
* const MockService = mock.cls(DataService, {
* selective: true,
* implementation: {
* getData: async () => 'mocked'
* }
* });
* ```
*
* @throws {Error} When constructor initialization fails
* @see {@link ClsMockOptions} For available configuration options
* @see {@link ClsMock} For the resulting mock class type
*/
function cls<T extends new (...args: any[]) => any>(originalClass: T, options?: ClsMockOptions<T>): ClsMock<T>;
/**
* Gets current configuration
*/
function getConfig(): Readonly<Config>;
/**
* Updates configuration
*/
function setConfig(options: Partial<Config>): void;
/**
* Resets configuration to defaults
*/
function resetConfig(): void;
/**
* Creates a scoped configuration
*/
function withConfig<T>(options: Partial<Config>, fn: () => T): T;
/**
* Gets configuration statistics
*/
function getConfigStats(): ConfigStats;
}
/**
* A universal spy implementation that provides advanced mocking capabilities.
* This class serves as the foundation for creating function spies with rich features
* such as call tracking, return value manipulation, and behavior verification.
*
* @template T - The type of function being spied on
*
* @example
* ```typescript
* // Create a spy for a simple function
* const spy = new UniversalSpy<() => string>();
* const spyFn = spy.getSpy();
*
* // Configure spy behavior
* spyFn.mockReturnValue('mocked');
*
* // Use the spy
* const result = spyFn();
* expect(result).toBe('mocked');
* expect(spyFn).toHaveBeenCalled();
* ```
*/
declare class UniversalSpy<T extends Fn = Fn> {
private tracker;
private implementation?;
private returnValue?;
private error?;
private spyFunction;
constructor(implementation?: T);
getSpy(): MockFunction<T>;
private updateState;
}
/**
* Creates a new spy function with optional implementation.
* The spy function tracks all calls and provides methods for verifying behavior.
*
* @template T - The type of function to spy on
* @param implementation - Optional implementation for the spy function
* @returns A mock function that tracks calls and provides verification methods
*
* @example
* ```typescript
* // Create a spy with no implementation
* const spy = createSpy<(name: string) => string>();
*
* // Create a spy with implementation
* const spy = createSpy((name: string) => `Hello ${name}`);
*
* // Use the spy
* spy('John');
* expect(spy).toHaveBeenCalledWith('John');
* ```
* @param implementation - Optional implementation for the spy
*/
declare function createSpy<T extends Fn>(implementation?: T): MockFunction<T>;
/**
* Replaces a method on an object with a spy while preserving the original implementation.
* This is useful for monitoring method calls while maintaining the original behavior.
*
* @template T - The type of object containing the method
* @param obj - The object containing the method to spy on
* @param key - The key of the method to spy on
* @param fn - The function to use as implementation
*
* @example
* ```typescript
* class Service {
* getData() { return 'data'; }
* }
*
* const service = new Service();
* replaceFn(service, 'getData', () => 'mock data');
*
* service.getData(); // Returns 'mock data'
* expect(service.getData).toHaveBeenCalled();
* ```
* Creates a spy for a property with getter and/or setter.
* @template T - The type of the property
*/
declare function replaceFn<T extends object>(obj: T, key: keyof T, fn: Fn): void;
/**
* Creates a spy for an existing method on an object.
* The spy will track all calls while maintaining the original implementation.
*
* @template T - The type of object containing the method
* @param obj - The object containing the method to spy on
* @param key - The key of the method to spy on
*
* @example
* ```typescript
* class Service {
* getData() { return 'data'; }
* }
*
* const service = new Service();
* spyOn(service, 'getData');
*
* service.getData(); // Returns 'data'
* expect(service.getData).toHaveBeenCalled();
* ```
*/
declare function spyOn<T extends object>(obj: T, key: keyof T): void;
/**
* Creates spies for getter/setter properties.
* This is useful for monitoring property access and modifications.
*
* @template T - The type of the property value
* @returns An object containing spy functions for get and set operations
*
* @example
* ```typescript
* class Service {
* private _value: string = '';
* get value() { return this._value; }
* set value(v: string) { this._value = v; }
* }
*
* const service = new Service();
* const spy = createPropertySpy<string>();
*
* Object.defineProperty(service, 'value', {
* get: spy.get,
* set: spy.set
* });
*
* service.value = 'test';
* expect(spy.set).toHaveBeenCalledWith('test');
* ```
*/
declare function createPropertySpy<T>(): {
get?: MockFunction;
set?: MockFunction;
get?: MockFunction<() => T>;
set?: MockFunction<(value: T) => void>;
};
export { type Adapter, type AsyncFn, type BaseConfig, type CacheEntry, type ClsMock, type ClsMockOptions, type Config, type ConfigStats, type Constructor, type DeepMock, type DeepPartial, type ExtendedWith, type Fn, type Framework, type JasmineSpy, type JestMock, type Matcher, type MatcherFactory, type MockBehavior$1 as MockBehavior, type MockBehaviorConfig, MockCache, type MockCall, type MockCallBehavior, type MockClassOptions, type MockClassResult, MockError, MockErrorCode, type MockFunction, type MockObjectOptions, type MockObjectResult, type MockOf, type PartialBuilder, type PartialOptions, type PropertyDescriptor$1 as PropertyDescriptor, type RecursivePartial, type SpyAdapter, type StaticMockOf, UniversalSpy, configManager, createPropertySpy, createSpy, mock, mockConfig, replaceFn, spyOn };
export { type MockFunction, createPropertySpy, createSpy };
'use strict';
// src/config.ts
var defaultConfig = {
debug: process.env.NODE_ENV === "development",
trackCalls: true,
allowUndefined: true,
strict: false,
timeout: 5e3,
preservePrototype: true
};
function isValidConfigValue(key, value) {
switch (key) {
case "debug":
case "trackCalls":
case "allowUndefined":
case "strict":
case "preservePrototype":
return typeof value === "boolean";
case "timeout":
return typeof value === "number" && value > 0;
default:
return false;
}
}
var GlobalConfig = class _GlobalConfig {
constructor() {
this.cachedConfig = null;
this.lastUpdate = 0;
this.updateCount = 0;
this.cacheHits = 0;
this.cacheTimeout = 1e3;
this.config = { ...defaultConfig };
}
/**
* Gets the singleton instance
*/
static getInstance() {
if (!_GlobalConfig.instance) {
_GlobalConfig.instance = new _GlobalConfig();
}
return _GlobalConfig.instance;
}
/**
* Gets the current configuration
*/
getConfig() {
if (this.cachedConfig && Date.now() - this.lastUpdate < this.cacheTimeout) {
this.cacheHits++;
return this.cachedConfig;
}
this.cachedConfig = Object.freeze({ ...this.config });
return this.cachedConfig;
}
/**
* Updates the configuration
*/
updateConfig(options) {
const newConfig = { ...this.config };
let hasChanges = false;
Object.keys(options).forEach((key) => {
if (key in defaultConfig && isValidConfigValue(key, options[key])) {
if (this.config[key] !== options[key]) {
newConfig[key] = options[key];
hasChanges = true;
}
}
});
if (hasChanges) {
this.config = newConfig;
this.invalidateCache();
this.updateCount++;
}
}
/**
* Resets configuration to defaults
*/
resetConfig() {
const newConfig = {
...defaultConfig,
debug: process.env.NODE_ENV === "development"
};
if (this.hasConfigChanged(newConfig)) {
this.config = newConfig;
this.invalidateCache();
this.updateCount++;
}
}
/**
* Gets update statistics
*/
getStats() {
return {
updateCount: this.updateCount,
lastUpdate: this.lastUpdate,
cacheHits: this.cacheHits
};
}
/**
* Creates a scoped configuration
*/
withConfig(options, fn) {
const previous = { ...this.getConfig() };
try {
this.updateConfig(options);
return fn();
} finally {
this.updateConfig(previous);
}
}
/**
* Checks if configuration has changed
*/
hasConfigChanged(newConfig) {
return Object.entries(newConfig).some(([key, value]) => {
const configKey = key;
return this.config[configKey] !== value;
});
}
/**
* Invalidates the configuration cache
*/
invalidateCache() {
this.cachedConfig = null;
this.lastUpdate = Date.now();
}
};
var configManager = {
/**
* Gets current configuration
*/
get() {
return GlobalConfig.getInstance().getConfig();
},
/**
* Updates configuration
*/
set(options) {
GlobalConfig.getInstance().updateConfig(options);
},
/**
* Resets configuration to defaults
*/
reset() {
GlobalConfig.getInstance().resetConfig();
},
/**
* Creates a scoped configuration
*/
with(options, fn) {
return GlobalConfig.getInstance().withConfig(options, fn);
},
/**
* Gets configuration statistics
*/
getStats() {
return GlobalConfig.getInstance().getStats();
}
};
var mockConfig = configManager;
// src/errors.ts
var MockErrorCode = /* @__PURE__ */ ((MockErrorCode2) => {
MockErrorCode2["CIRCULAR_REFERENCE"] = "CIRCULAR_REF";
MockErrorCode2["INVALID_TARGET"] = "INVALID_TARGET";
MockErrorCode2["INITIALIZATION_FAILED"] = "INIT_FAILED";
MockErrorCode2["TYPE_MISMATCH"] = "TYPE_MISMATCH";
MockErrorCode2["INVALID_IMPLEMENTATION"] = "INVALID_IMPL";
MockErrorCode2["INVALID_CONFIGURATION"] = "INVALID_CONFIG";
MockErrorCode2["VERIFICATION_ERROR"] = "VERIFY_ERROR";
MockErrorCode2["EXPECTATION_FAILED"] = "EXPECT_FAILED";
MockErrorCode2["PERFORMANCE_CONSTRAINT_VIOLATED"] = "PERF_VIOLATED";
return MockErrorCode2;
})(MockErrorCode || {});
var MockError = class _MockError extends Error {

@@ -447,9 +276,16 @@ constructor(code, message, details) {

} else if (this.implementation) {
result = this.implementation.apply(spy._this || this, args);
result = this.implementation.apply(spy._this, args);
} else {
result = void 0;
}
spy.mock.calls.push(args);
spy.mock.results.push({ type: "return", value: result });
if (spy._this) {
spy.mock.instances.push(spy._this);
spy.mock.contexts.push(spy._this);
}
finishCall(void 0, result);
return result;
} catch (error) {
spy.mock.results.push({ type: "throw", value: error });
finishCall(error instanceof Error ? error : new Error(String(error)));

@@ -471,19 +307,26 @@ throw error;

});
const boundSpy = new Proxy(spy, {
apply: (target, thisArg, argumentsList) => {
spy._this = thisArg;
const result = target.apply(thisArg, argumentsList);
spy._this = null;
return result;
}
spy.calls = {
all: () => this.tracker.getCallsFor("spy").map((call) => ({ args: call.args })),
count: () => this.tracker.getCallsFor("spy").length
};
Object.defineProperty(spy, "mock", {
value: {
calls: [],
results: [],
instances: [],
contexts: []
},
writable: true,
configurable: true,
enumerable: true
});
boundSpy.mockReturnValue = (value) => {
spy.mockReturnValue = (value) => {
this.updateState({ returnValue: value });
return boundSpy;
return spy;
};
boundSpy.mockResolvedValue = (value) => {
spy.mockResolvedValue = (value) => {
this.updateState({ returnValue: Promise.resolve(value) });
return boundSpy;
return spy;
};
boundSpy.mockImplementation = (fn) => {
spy.mockImplementation = (fn) => {
if (fn !== null && fn !== void 0 && typeof fn !== "function") {

@@ -493,36 +336,52 @@ throw new Error("Mock implementation must be a function");

this.updateState({ implementation: fn });
return boundSpy;
return spy;
};
boundSpy.mockReset = () => {
spy.mockReset = () => {
this.tracker.reset();
this.updateState({});
};
boundSpy.mockClear = () => {
spy.mockClear = () => {
this.tracker.reset();
};
boundSpy.calls = {
all: () => this.tracker.getCallsFor("spy").map((call) => ({ args: call.args })),
count: () => this.tracker.getCallsFor("spy").length
};
boundSpy.and = {
returnValue: (value) => {
boundSpy.mockReturnValue(value);
return boundSpy;
},
callFake: (fn) => {
boundSpy.mockImplementation(fn);
return boundSpy;
},
const jasmineApi = {
returnValue: spy.mockReturnValue,
callFake: spy.mockImplementation,
throwError: (error) => {
this.updateState({ error });
return boundSpy;
return spy;
},
stub: () => {
boundSpy.mockImplementation(void 0);
return boundSpy;
spy.mockReset();
return spy;
}
};
boundSpy.toHaveBeenCalled = () => this.tracker.getCallsFor("spy").length > 0;
boundSpy.toHaveBeenCalledTimes = (n) => this.tracker.getCallsFor("spy").length === n;
boundSpy.toHaveBeenCalledWith = (...args) => this.tracker.getCallsFor("spy").some((call) => call.args.length === args.length && call.args.every((arg, i) => arg === args[i]));
Object.defineProperty(spy, "and", {
value: jasmineApi,
writable: false,
enumerable: true,
configurable: true
});
spy.toHaveBeenCalled = () => this.tracker.getCallsFor("spy").length > 0;
spy.toHaveBeenCalledTimes = (n) => this.tracker.getCallsFor("spy").length === n;
spy.toHaveBeenCalledWith = (...args) => this.tracker.getCallsFor("spy").some((call) => call.args.length === args.length && call.args.every((arg, i) => arg === args[i]));
const boundSpy = new Proxy(spy, {
apply: (target, thisArg, argumentsList) => {
spy._this = thisArg;
const result = target.apply(thisArg, argumentsList);
spy._this = null;
return result;
},
get: (target, prop) => {
const value = target[prop];
if (typeof value === "function") {
return function(...args) {
spy._this = this;
const result = value.apply(this, args);
spy._this = null;
return result;
};
}
return value;
}
});
this.spyFunction = boundSpy;

@@ -559,23 +418,2 @@ }

}
function replaceFn(obj, key, fn) {
const spy = createSpy(fn);
spy.mockImplementation(fn);
Object.defineProperty(obj, key, {
value: spy,
writable: true,
configurable: true
});
}
function spyOn(obj, key) {
const value = obj[key];
if (typeof value === "function") {
const spy = createSpy(value);
spy.mockImplementation(value);
Object.defineProperty(obj, key, {
value: spy,
writable: true,
configurable: true
});
}
}
function createPropertySpy() {

@@ -588,588 +426,5 @@ return {

// src/mock.ts
function mock(targetOrImpl, optionsOrImpl) {
const mockOptions = optionsOrImpl && "trackCalls" in optionsOrImpl ? optionsOrImpl : {};
const currentConfig = {
...mock.getConfig(),
...mockOptions
};
if (currentConfig.debug) {
console.debug("Mocking:", targetOrImpl);
}
if (!targetOrImpl) {
return mock.object();
}
if (typeof targetOrImpl === "function" && targetOrImpl.prototype) {
return mock.cls(targetOrImpl, optionsOrImpl);
}
if (targetOrImpl && typeof targetOrImpl === "object" && optionsOrImpl) {
return mock.partial(targetOrImpl).with(optionsOrImpl);
}
return mock.of(targetOrImpl);
}
((mock2) => {
function fn() {
return createSpy();
}
mock2.fn = fn;
function object() {
const target = {};
const cache = /* @__PURE__ */ new Map();
const descriptors = /* @__PURE__ */ new Map();
const refs = /* @__PURE__ */ new WeakMap();
const config = mock2.getConfig();
const handler = {
get: (_, prop) => {
if (prop === "then") {
return void 0;
}
const descriptor = descriptors.get(prop);
if (descriptor) {
return descriptor.get?.();
}
if (!cache.has(prop)) {
if (!config.allowUndefined && !prop.toString().startsWith("__")) {
if (config.strict) {
throw new Error(`Accessing undefined property: ${String(prop)}`);
}
if (config.debug) {
console.warn(`Accessing undefined property: ${String(prop)}`);
}
}
const spy = createSpy();
if (config.trackCalls) {
spy.mockImplementation(function(...args) {
if (config.debug) {
console.debug(`Called ${String(prop)} with:`, args);
}
return void 0;
});
}
cache.set(prop, spy);
return spy;
}
return cache.get(prop);
},
set: (_, prop, value) => {
if (value && typeof value === "object") {
if (refs.has(value)) {
cache.set(prop, value);
return true;
}
refs.set(value, true);
}
cache.delete(prop);
cache.set(prop, value);
return true;
},
defineProperty: (_, prop, descriptor) => {
descriptors.set(prop, {
...descriptor,
configurable: true,
enumerable: true
});
return true;
},
getOwnPropertyDescriptor: (_, prop) => {
const descriptor = descriptors.get(prop);
if (descriptor) {
return {
...descriptor,
configurable: true,
enumerable: true
};
}
if (cache.has(prop)) {
return {
value: cache.get(prop),
writable: true,
configurable: true,
enumerable: true
};
}
return void 0;
},
has: (_, prop) => {
return descriptors.has(prop) || cache.has(prop);
},
ownKeys: () => {
return [...descriptors.keys(), ...cache.keys()];
}
};
return new Proxy(target, handler);
}
mock2.object = object;
function partial(base, stubs, options) {
const config = mock2.getConfig();
const defaultOptions = {
selective: false,
preserveThis: config.preservePrototype,
autoSpy: config.trackCalls,
handleCircular: false
};
const spyMethods = /* @__PURE__ */ new Set();
const preserveProps = /* @__PURE__ */ new Set();
function createMock(withStubs = {}, withOptions = {}) {
const finalOptions = { ...defaultOptions, ...withOptions };
if (config.debug) {
console.debug("Creating partial mock for:", base.constructor.name);
}
const result = Object.create(finalOptions.preserveThis ? Object.getPrototypeOf(base) : null);
Object.getOwnPropertyNames(base).forEach((key) => {
const typedKey = key;
if (!Object.prototype.hasOwnProperty.call(withStubs, key) && !spyMethods.has(typedKey)) {
if (preserveProps.has(typedKey)) {
result[typedKey] = base[typedKey];
} else {
const descriptor = Object.getOwnPropertyDescriptor(base, key);
if (typeof descriptor.value === "function" && !finalOptions.selective) {
const spy = createSpy();
spy.mockImplementation(descriptor.value.bind(result));
result[typedKey] = spy;
} else {
Object.defineProperty(result, key, descriptor);
}
}
}
});
try {
if (finalOptions.handleCircular) {
const merged = { ...base, ...withStubs };
Object.assign(result, merged);
for (const key in merged) {
const value = merged[key];
if (value === base) {
result[key] = result;
}
}
} else {
Object.keys(withStubs).forEach((key) => {
const typedKey = key;
if (preserveProps.has(typedKey)) {
result[typedKey] = base[typedKey];
return;
}
const value = withStubs[key];
if (typeof value === "function") {
const spy = createSpy();
spy.mockImplementation(finalOptions.preserveThis ? value.bind(result) : value);
result[typedKey] = spy;
} else if (value && typeof value === "object" && !Array.isArray(value)) {
if (value === base && !finalOptions.handleCircular) {
throw new Error(`Property ${String(typedKey)} has a circular reference`);
}
try {
result[typedKey] = { ...base[typedKey], ...value };
} catch (e) {
if (e instanceof RangeError) {
throw new Error(
`Property ${String(typedKey)} has a circular reference.
Consider using handleCircular: true option.`
);
}
throw e;
}
} else {
result[typedKey] = value;
}
});
}
} catch (e) {
if (e instanceof RangeError && finalOptions.handleCircular) {
return mock2.cast({ ...base, ...withStubs });
}
throw e;
}
spyMethods.forEach((method) => {
if (typeof result[method] === "function" && !preserveProps.has(method)) {
const original = result[method];
const spy = createSpy();
spy.mockImplementation(finalOptions.preserveThis ? original.bind(result) : original);
result[method] = spy;
}
});
return result;
}
const builder = {
with: (withStubs, withOptions) => createMock(withStubs, withOptions),
spy: (...methods) => {
methods.forEach((method) => spyMethods.add(method));
return builder;
},
preserve: (...properties) => {
properties.forEach((prop) => preserveProps.add(prop));
return builder;
}
};
return stubs ? createMock(stubs, options) : builder;
}
mock2.partial = partial;
function cast(partial2) {
const proxies = /* @__PURE__ */ new WeakMap();
const handler = {
get: (target, prop) => {
if (prop === "then") {
return void 0;
}
if (prop in target) {
const value = target[prop];
if (value && typeof value === "object") {
if (proxies.has(value)) {
return proxies.get(value);
}
const proxy2 = new Proxy(value, handler);
proxies.set(value, proxy2);
return proxy2;
}
return value;
}
return void 0;
}
};
const proxy = new Proxy(partial2, handler);
if (partial2 && typeof partial2 === "object") {
proxies.set(partial2, proxy);
}
return proxy;
}
mock2.cast = cast;
function of(stubs = {}) {
if (Array.isArray(stubs)) {
return stubs.map((item) => of(item));
}
const base = object();
const result = Object.create(Object.getPrototypeOf(base));
const cache = /* @__PURE__ */ new Map();
Object.entries(stubs).forEach(([key, value]) => {
result[key] = value;
});
return new Proxy(result, {
get(target, prop) {
if (prop === "then") return void 0;
if (cache.has(prop)) {
return cache.get(prop);
}
const value = target[prop];
if (typeof value === "function") {
const spy2 = createSpy();
spy2.mockImplementation(value);
cache.set(prop, spy2);
return spy2;
}
if (value !== void 0) {
cache.set(prop, value);
return value;
}
const spy = createSpy();
cache.set(prop, spy);
return spy;
}
});
}
mock2.of = of;
const ORIGINAL_PREFIX = "__original_";
const EXCLUDED_PROPS = ["name", "length", "prototype", "_isMockFunction"];
function replace(obj, key, impl) {
if (!obj || typeof key !== "string") return;
const methodKey = String(key);
const originalKey = `${ORIGINAL_PREFIX}${methodKey}`;
const original = obj[key];
if (!(originalKey in obj)) {
obj[originalKey] = original;
}
const spy = createSpy();
spy.mockImplementation(impl);
if (typeof original === "function") {
Object.getOwnPropertyNames(original).forEach((prop) => {
if (!EXCLUDED_PROPS.includes(prop)) {
spy[prop] = original[prop];
}
});
}
obj[key] = spy;
}
mock2.replace = replace;
function restore(obj, key) {
if (!obj) return false;
const restoreMethod = (methodKey) => {
const originalKey = `${ORIGINAL_PREFIX}${methodKey}`;
if (!(originalKey in obj)) return false;
const original = obj[originalKey];
const current = obj[methodKey];
if (current && typeof current === "function") {
Object.getOwnPropertyNames(current).forEach((prop) => {
if (!EXCLUDED_PROPS.includes(prop)) {
try {
original[prop] = current[prop];
} catch (e) {
}
}
});
}
try {
obj[methodKey] = original;
delete obj[originalKey];
return true;
} catch (e) {
return false;
}
};
if (key !== void 0) {
return restoreMethod(String(key));
}
let anyRestored = false;
const keys = Object.getOwnPropertyNames(obj);
for (const k of keys) {
if (k.startsWith(ORIGINAL_PREFIX)) {
const methodKey = k.slice(ORIGINAL_PREFIX.length);
if (restoreMethod(methodKey)) {
anyRestored = true;
}
}
}
return anyRestored;
}
mock2.restore = restore;
function getReplacedMethods(obj) {
if (!obj) return [];
return Object.getOwnPropertyNames(obj).filter((k) => k.startsWith(ORIGINAL_PREFIX)).map((k) => k.slice(ORIGINAL_PREFIX.length)).filter((k) => k && typeof obj[k] !== "undefined");
}
mock2.getReplacedMethods = getReplacedMethods;
function isReplaced(obj, key) {
if (!obj) return false;
const originalKey = `${ORIGINAL_PREFIX}${String(key)}`;
const original = obj[originalKey];
const current = obj[key];
return originalKey in obj && current !== original;
}
mock2.isReplaced = isReplaced;
function verifyRestored(obj, key) {
if (!obj) return false;
const originalKey = `${ORIGINAL_PREFIX}${String(key)}`;
const original = obj[originalKey];
if (!original) return true;
const current = obj[key];
if (current === original) return true;
if (typeof current === "function" && typeof original === "function") {
try {
const currentStr = current.toString().replace(/\s+/g, "");
const originalStr = original.toString().replace(/\s+/g, "");
return currentStr === originalStr;
} catch {
return current === original;
}
}
return false;
}
mock2.verifyRestored = verifyRestored;
function handleStaticMethodInheritance(mockClass, originalClass) {
let currentProto = Object.getPrototypeOf(originalClass);
while (currentProto && currentProto !== Function.prototype) {
Object.getOwnPropertyNames(currentProto).filter((prop) => typeof currentProto[prop] === "function").forEach((methodName) => {
if (!mockClass[methodName]) {
const spy = createSpy();
spy.mockImplementation(currentProto[methodName].bind(mockClass));
mockClass[methodName] = spy;
}
});
currentProto = Object.getPrototypeOf(currentProto);
}
}
function cls(originalClass, options = {}) {
const config = mock2.getConfig();
const { selective = false, implementation = {} } = options;
if (config.debug) {
console.debug("Creating class mock for:", originalClass.name);
}
const createMethodSpy = (method, context) => {
const spy = createSpy();
if (config.trackCalls) {
spy.mockImplementation((...args) => {
if (config.debug) {
console.debug(`Called ${method.name} with:`, args);
}
return method.apply(context, args);
});
} else {
spy.mockImplementation((...args) => method.apply(context, args));
}
return spy;
};
const handleDescriptor = (name, descriptor, context) => {
if (descriptor.get || descriptor.set) {
const spies = createPropertySpy();
const implDescriptor = typeof name === "string" ? Object.getOwnPropertyDescriptor(implementation, name) : void 0;
return {
configurable: true,
enumerable: true,
get: descriptor.get && (implDescriptor?.get ? spies.get?.mockImplementation(implDescriptor.get) : !selective ? spies.get?.mockImplementation(descriptor.get.bind(context)) : descriptor.get.bind(context)),
set: descriptor.set && (implDescriptor?.set ? spies.set?.mockImplementation(implDescriptor.set) : !selective ? spies.set?.mockImplementation(descriptor.set.bind(context)) : descriptor.set.bind(context))
};
}
if (typeof descriptor.value === "function") {
const impl = typeof name === "string" ? implementation[name] : void 0;
if (impl) {
const spy = createSpy();
spy.mockImplementation(impl);
return { ...descriptor, value: spy };
}
if (!selective) {
return {
...descriptor,
value: createMethodSpy(descriptor.value, context)
};
}
}
return {
...descriptor,
value: descriptor.value
};
};
function MockClass(...args) {
if (!(this instanceof MockClass)) {
return new MockClass(...args);
}
const instance = Object.create(originalClass.prototype);
try {
const temp = new originalClass(...args);
Object.assign(instance, temp);
} catch {
}
const processMembers = (target, source) => {
Object.getOwnPropertyNames(source).forEach((name) => {
if (name === "constructor") return;
if (!target.hasOwnProperty(name)) {
const descriptor = Object.getOwnPropertyDescriptor(source, name);
if (descriptor) {
Object.defineProperty(target, name, handleDescriptor(name, descriptor, instance));
}
}
});
};
processMembers(instance, originalClass.prototype);
let proto = Object.getPrototypeOf(originalClass.prototype);
while (proto && proto !== Object.prototype) {
processMembers(instance, proto);
proto = Object.getPrototypeOf(proto);
}
Object.setPrototypeOf(instance, MockClass.prototype);
return instance;
}
MockClass.prototype = Object.create(originalClass.prototype);
MockClass.prototype.constructor = MockClass;
Object.getOwnPropertyNames(originalClass).forEach((name) => {
if (name === "length" || name === "prototype" || name === "name") return;
const descriptor = Object.getOwnPropertyDescriptor(originalClass, name);
if (descriptor) {
Object.defineProperty(MockClass, name, handleDescriptor(name, descriptor, originalClass));
}
});
handleStaticMethodInheritance(MockClass, originalClass);
return MockClass;
}
mock2.cls = cls;
function getConfig() {
return configManager.get();
}
mock2.getConfig = getConfig;
function setConfig(options) {
configManager.set(options);
}
mock2.setConfig = setConfig;
function resetConfig() {
configManager.reset();
}
mock2.resetConfig = resetConfig;
function withConfig(options, fn2) {
return configManager.with(options, fn2);
}
mock2.withConfig = withConfig;
function getConfigStats() {
return configManager.getStats();
}
mock2.getConfigStats = getConfigStats;
})(mock);
// src/types/cache.ts
var MockCache = class {
constructor() {
this.cache = /* @__PURE__ */ new Map();
this.registry = new FinalizationRegistry((key) => {
this.cache.delete(key);
});
}
/**
* Set a value in the cache with optional descriptor
*/
set(key, value, descriptor) {
const entry = {
value,
descriptor,
timestamp: Date.now()
};
if (value && typeof value === "object") {
entry.ref = new WeakRef(value);
this.registry.register(value, String(key));
}
this.cache.set(key, entry);
}
/**
* Get a value from the cache
*/
get(key) {
const entry = this.cache.get(key);
if (!entry) return void 0;
if (entry.ref) {
const value = entry.ref.deref();
if (!value) {
this.cache.delete(key);
return void 0;
}
}
return entry.value;
}
/**
* Get a property descriptor from the cache
*/
getDescriptor(key) {
return this.cache.get(key)?.descriptor;
}
/**
* Check if a key exists in the cache
*/
has(key) {
return this.cache.has(key);
}
/**
* Delete a key from the cache
*/
delete(key) {
this.cache.delete(key);
}
/**
* Clear all entries from the cache
*/
clear() {
this.cache.clear();
}
/**
* Get all keys in the cache
*/
keys() {
return this.cache.keys();
}
};
exports.MockCache = MockCache;
exports.MockError = MockError;
exports.MockErrorCode = MockErrorCode;
exports.UniversalSpy = UniversalSpy;
exports.configManager = configManager;
exports.createPropertySpy = createPropertySpy;
exports.createSpy = createSpy;
exports.mock = mock;
exports.mockConfig = mockConfig;
exports.replaceFn = replaceFn;
exports.spyOn = spyOn;
//# sourceMappingURL=index.js.map
//# sourceMappingURL=index.js.map

@@ -1,173 +0,2 @@

// src/config.ts
var defaultConfig = {
debug: process.env.NODE_ENV === "development",
trackCalls: true,
allowUndefined: true,
strict: false,
timeout: 5e3,
preservePrototype: true
};
function isValidConfigValue(key, value) {
switch (key) {
case "debug":
case "trackCalls":
case "allowUndefined":
case "strict":
case "preservePrototype":
return typeof value === "boolean";
case "timeout":
return typeof value === "number" && value > 0;
default:
return false;
}
}
var GlobalConfig = class _GlobalConfig {
constructor() {
this.cachedConfig = null;
this.lastUpdate = 0;
this.updateCount = 0;
this.cacheHits = 0;
this.cacheTimeout = 1e3;
this.config = { ...defaultConfig };
}
/**
* Gets the singleton instance
*/
static getInstance() {
if (!_GlobalConfig.instance) {
_GlobalConfig.instance = new _GlobalConfig();
}
return _GlobalConfig.instance;
}
/**
* Gets the current configuration
*/
getConfig() {
if (this.cachedConfig && Date.now() - this.lastUpdate < this.cacheTimeout) {
this.cacheHits++;
return this.cachedConfig;
}
this.cachedConfig = Object.freeze({ ...this.config });
return this.cachedConfig;
}
/**
* Updates the configuration
*/
updateConfig(options) {
const newConfig = { ...this.config };
let hasChanges = false;
Object.keys(options).forEach((key) => {
if (key in defaultConfig && isValidConfigValue(key, options[key])) {
if (this.config[key] !== options[key]) {
newConfig[key] = options[key];
hasChanges = true;
}
}
});
if (hasChanges) {
this.config = newConfig;
this.invalidateCache();
this.updateCount++;
}
}
/**
* Resets configuration to defaults
*/
resetConfig() {
const newConfig = {
...defaultConfig,
debug: process.env.NODE_ENV === "development"
};
if (this.hasConfigChanged(newConfig)) {
this.config = newConfig;
this.invalidateCache();
this.updateCount++;
}
}
/**
* Gets update statistics
*/
getStats() {
return {
updateCount: this.updateCount,
lastUpdate: this.lastUpdate,
cacheHits: this.cacheHits
};
}
/**
* Creates a scoped configuration
*/
withConfig(options, fn) {
const previous = { ...this.getConfig() };
try {
this.updateConfig(options);
return fn();
} finally {
this.updateConfig(previous);
}
}
/**
* Checks if configuration has changed
*/
hasConfigChanged(newConfig) {
return Object.entries(newConfig).some(([key, value]) => {
const configKey = key;
return this.config[configKey] !== value;
});
}
/**
* Invalidates the configuration cache
*/
invalidateCache() {
this.cachedConfig = null;
this.lastUpdate = Date.now();
}
};
var configManager = {
/**
* Gets current configuration
*/
get() {
return GlobalConfig.getInstance().getConfig();
},
/**
* Updates configuration
*/
set(options) {
GlobalConfig.getInstance().updateConfig(options);
},
/**
* Resets configuration to defaults
*/
reset() {
GlobalConfig.getInstance().resetConfig();
},
/**
* Creates a scoped configuration
*/
with(options, fn) {
return GlobalConfig.getInstance().withConfig(options, fn);
},
/**
* Gets configuration statistics
*/
getStats() {
return GlobalConfig.getInstance().getStats();
}
};
var mockConfig = configManager;
// src/errors.ts
var MockErrorCode = /* @__PURE__ */ ((MockErrorCode2) => {
MockErrorCode2["CIRCULAR_REFERENCE"] = "CIRCULAR_REF";
MockErrorCode2["INVALID_TARGET"] = "INVALID_TARGET";
MockErrorCode2["INITIALIZATION_FAILED"] = "INIT_FAILED";
MockErrorCode2["TYPE_MISMATCH"] = "TYPE_MISMATCH";
MockErrorCode2["INVALID_IMPLEMENTATION"] = "INVALID_IMPL";
MockErrorCode2["INVALID_CONFIGURATION"] = "INVALID_CONFIG";
MockErrorCode2["VERIFICATION_ERROR"] = "VERIFY_ERROR";
MockErrorCode2["EXPECTATION_FAILED"] = "EXPECT_FAILED";
MockErrorCode2["PERFORMANCE_CONSTRAINT_VIOLATED"] = "PERF_VIOLATED";
return MockErrorCode2;
})(MockErrorCode || {});
var MockError = class _MockError extends Error {

@@ -445,9 +274,16 @@ constructor(code, message, details) {

} else if (this.implementation) {
result = this.implementation.apply(spy._this || this, args);
result = this.implementation.apply(spy._this, args);
} else {
result = void 0;
}
spy.mock.calls.push(args);
spy.mock.results.push({ type: "return", value: result });
if (spy._this) {
spy.mock.instances.push(spy._this);
spy.mock.contexts.push(spy._this);
}
finishCall(void 0, result);
return result;
} catch (error) {
spy.mock.results.push({ type: "throw", value: error });
finishCall(error instanceof Error ? error : new Error(String(error)));

@@ -469,19 +305,26 @@ throw error;

});
const boundSpy = new Proxy(spy, {
apply: (target, thisArg, argumentsList) => {
spy._this = thisArg;
const result = target.apply(thisArg, argumentsList);
spy._this = null;
return result;
}
spy.calls = {
all: () => this.tracker.getCallsFor("spy").map((call) => ({ args: call.args })),
count: () => this.tracker.getCallsFor("spy").length
};
Object.defineProperty(spy, "mock", {
value: {
calls: [],
results: [],
instances: [],
contexts: []
},
writable: true,
configurable: true,
enumerable: true
});
boundSpy.mockReturnValue = (value) => {
spy.mockReturnValue = (value) => {
this.updateState({ returnValue: value });
return boundSpy;
return spy;
};
boundSpy.mockResolvedValue = (value) => {
spy.mockResolvedValue = (value) => {
this.updateState({ returnValue: Promise.resolve(value) });
return boundSpy;
return spy;
};
boundSpy.mockImplementation = (fn) => {
spy.mockImplementation = (fn) => {
if (fn !== null && fn !== void 0 && typeof fn !== "function") {

@@ -491,36 +334,52 @@ throw new Error("Mock implementation must be a function");

this.updateState({ implementation: fn });
return boundSpy;
return spy;
};
boundSpy.mockReset = () => {
spy.mockReset = () => {
this.tracker.reset();
this.updateState({});
};
boundSpy.mockClear = () => {
spy.mockClear = () => {
this.tracker.reset();
};
boundSpy.calls = {
all: () => this.tracker.getCallsFor("spy").map((call) => ({ args: call.args })),
count: () => this.tracker.getCallsFor("spy").length
};
boundSpy.and = {
returnValue: (value) => {
boundSpy.mockReturnValue(value);
return boundSpy;
},
callFake: (fn) => {
boundSpy.mockImplementation(fn);
return boundSpy;
},
const jasmineApi = {
returnValue: spy.mockReturnValue,
callFake: spy.mockImplementation,
throwError: (error) => {
this.updateState({ error });
return boundSpy;
return spy;
},
stub: () => {
boundSpy.mockImplementation(void 0);
return boundSpy;
spy.mockReset();
return spy;
}
};
boundSpy.toHaveBeenCalled = () => this.tracker.getCallsFor("spy").length > 0;
boundSpy.toHaveBeenCalledTimes = (n) => this.tracker.getCallsFor("spy").length === n;
boundSpy.toHaveBeenCalledWith = (...args) => this.tracker.getCallsFor("spy").some((call) => call.args.length === args.length && call.args.every((arg, i) => arg === args[i]));
Object.defineProperty(spy, "and", {
value: jasmineApi,
writable: false,
enumerable: true,
configurable: true
});
spy.toHaveBeenCalled = () => this.tracker.getCallsFor("spy").length > 0;
spy.toHaveBeenCalledTimes = (n) => this.tracker.getCallsFor("spy").length === n;
spy.toHaveBeenCalledWith = (...args) => this.tracker.getCallsFor("spy").some((call) => call.args.length === args.length && call.args.every((arg, i) => arg === args[i]));
const boundSpy = new Proxy(spy, {
apply: (target, thisArg, argumentsList) => {
spy._this = thisArg;
const result = target.apply(thisArg, argumentsList);
spy._this = null;
return result;
},
get: (target, prop) => {
const value = target[prop];
if (typeof value === "function") {
return function(...args) {
spy._this = this;
const result = value.apply(this, args);
spy._this = null;
return result;
};
}
return value;
}
});
this.spyFunction = boundSpy;

@@ -557,23 +416,2 @@ }

}
function replaceFn(obj, key, fn) {
const spy = createSpy(fn);
spy.mockImplementation(fn);
Object.defineProperty(obj, key, {
value: spy,
writable: true,
configurable: true
});
}
function spyOn(obj, key) {
const value = obj[key];
if (typeof value === "function") {
const spy = createSpy(value);
spy.mockImplementation(value);
Object.defineProperty(obj, key, {
value: spy,
writable: true,
configurable: true
});
}
}
function createPropertySpy() {

@@ -586,578 +424,4 @@ return {

// src/mock.ts
function mock(targetOrImpl, optionsOrImpl) {
const mockOptions = optionsOrImpl && "trackCalls" in optionsOrImpl ? optionsOrImpl : {};
const currentConfig = {
...mock.getConfig(),
...mockOptions
};
if (currentConfig.debug) {
console.debug("Mocking:", targetOrImpl);
}
if (!targetOrImpl) {
return mock.object();
}
if (typeof targetOrImpl === "function" && targetOrImpl.prototype) {
return mock.cls(targetOrImpl, optionsOrImpl);
}
if (targetOrImpl && typeof targetOrImpl === "object" && optionsOrImpl) {
return mock.partial(targetOrImpl).with(optionsOrImpl);
}
return mock.of(targetOrImpl);
}
((mock2) => {
function fn() {
return createSpy();
}
mock2.fn = fn;
function object() {
const target = {};
const cache = /* @__PURE__ */ new Map();
const descriptors = /* @__PURE__ */ new Map();
const refs = /* @__PURE__ */ new WeakMap();
const config = mock2.getConfig();
const handler = {
get: (_, prop) => {
if (prop === "then") {
return void 0;
}
const descriptor = descriptors.get(prop);
if (descriptor) {
return descriptor.get?.();
}
if (!cache.has(prop)) {
if (!config.allowUndefined && !prop.toString().startsWith("__")) {
if (config.strict) {
throw new Error(`Accessing undefined property: ${String(prop)}`);
}
if (config.debug) {
console.warn(`Accessing undefined property: ${String(prop)}`);
}
}
const spy = createSpy();
if (config.trackCalls) {
spy.mockImplementation(function(...args) {
if (config.debug) {
console.debug(`Called ${String(prop)} with:`, args);
}
return void 0;
});
}
cache.set(prop, spy);
return spy;
}
return cache.get(prop);
},
set: (_, prop, value) => {
if (value && typeof value === "object") {
if (refs.has(value)) {
cache.set(prop, value);
return true;
}
refs.set(value, true);
}
cache.delete(prop);
cache.set(prop, value);
return true;
},
defineProperty: (_, prop, descriptor) => {
descriptors.set(prop, {
...descriptor,
configurable: true,
enumerable: true
});
return true;
},
getOwnPropertyDescriptor: (_, prop) => {
const descriptor = descriptors.get(prop);
if (descriptor) {
return {
...descriptor,
configurable: true,
enumerable: true
};
}
if (cache.has(prop)) {
return {
value: cache.get(prop),
writable: true,
configurable: true,
enumerable: true
};
}
return void 0;
},
has: (_, prop) => {
return descriptors.has(prop) || cache.has(prop);
},
ownKeys: () => {
return [...descriptors.keys(), ...cache.keys()];
}
};
return new Proxy(target, handler);
}
mock2.object = object;
function partial(base, stubs, options) {
const config = mock2.getConfig();
const defaultOptions = {
selective: false,
preserveThis: config.preservePrototype,
autoSpy: config.trackCalls,
handleCircular: false
};
const spyMethods = /* @__PURE__ */ new Set();
const preserveProps = /* @__PURE__ */ new Set();
function createMock(withStubs = {}, withOptions = {}) {
const finalOptions = { ...defaultOptions, ...withOptions };
if (config.debug) {
console.debug("Creating partial mock for:", base.constructor.name);
}
const result = Object.create(finalOptions.preserveThis ? Object.getPrototypeOf(base) : null);
Object.getOwnPropertyNames(base).forEach((key) => {
const typedKey = key;
if (!Object.prototype.hasOwnProperty.call(withStubs, key) && !spyMethods.has(typedKey)) {
if (preserveProps.has(typedKey)) {
result[typedKey] = base[typedKey];
} else {
const descriptor = Object.getOwnPropertyDescriptor(base, key);
if (typeof descriptor.value === "function" && !finalOptions.selective) {
const spy = createSpy();
spy.mockImplementation(descriptor.value.bind(result));
result[typedKey] = spy;
} else {
Object.defineProperty(result, key, descriptor);
}
}
}
});
try {
if (finalOptions.handleCircular) {
const merged = { ...base, ...withStubs };
Object.assign(result, merged);
for (const key in merged) {
const value = merged[key];
if (value === base) {
result[key] = result;
}
}
} else {
Object.keys(withStubs).forEach((key) => {
const typedKey = key;
if (preserveProps.has(typedKey)) {
result[typedKey] = base[typedKey];
return;
}
const value = withStubs[key];
if (typeof value === "function") {
const spy = createSpy();
spy.mockImplementation(finalOptions.preserveThis ? value.bind(result) : value);
result[typedKey] = spy;
} else if (value && typeof value === "object" && !Array.isArray(value)) {
if (value === base && !finalOptions.handleCircular) {
throw new Error(`Property ${String(typedKey)} has a circular reference`);
}
try {
result[typedKey] = { ...base[typedKey], ...value };
} catch (e) {
if (e instanceof RangeError) {
throw new Error(
`Property ${String(typedKey)} has a circular reference.
Consider using handleCircular: true option.`
);
}
throw e;
}
} else {
result[typedKey] = value;
}
});
}
} catch (e) {
if (e instanceof RangeError && finalOptions.handleCircular) {
return mock2.cast({ ...base, ...withStubs });
}
throw e;
}
spyMethods.forEach((method) => {
if (typeof result[method] === "function" && !preserveProps.has(method)) {
const original = result[method];
const spy = createSpy();
spy.mockImplementation(finalOptions.preserveThis ? original.bind(result) : original);
result[method] = spy;
}
});
return result;
}
const builder = {
with: (withStubs, withOptions) => createMock(withStubs, withOptions),
spy: (...methods) => {
methods.forEach((method) => spyMethods.add(method));
return builder;
},
preserve: (...properties) => {
properties.forEach((prop) => preserveProps.add(prop));
return builder;
}
};
return stubs ? createMock(stubs, options) : builder;
}
mock2.partial = partial;
function cast(partial2) {
const proxies = /* @__PURE__ */ new WeakMap();
const handler = {
get: (target, prop) => {
if (prop === "then") {
return void 0;
}
if (prop in target) {
const value = target[prop];
if (value && typeof value === "object") {
if (proxies.has(value)) {
return proxies.get(value);
}
const proxy2 = new Proxy(value, handler);
proxies.set(value, proxy2);
return proxy2;
}
return value;
}
return void 0;
}
};
const proxy = new Proxy(partial2, handler);
if (partial2 && typeof partial2 === "object") {
proxies.set(partial2, proxy);
}
return proxy;
}
mock2.cast = cast;
function of(stubs = {}) {
if (Array.isArray(stubs)) {
return stubs.map((item) => of(item));
}
const base = object();
const result = Object.create(Object.getPrototypeOf(base));
const cache = /* @__PURE__ */ new Map();
Object.entries(stubs).forEach(([key, value]) => {
result[key] = value;
});
return new Proxy(result, {
get(target, prop) {
if (prop === "then") return void 0;
if (cache.has(prop)) {
return cache.get(prop);
}
const value = target[prop];
if (typeof value === "function") {
const spy2 = createSpy();
spy2.mockImplementation(value);
cache.set(prop, spy2);
return spy2;
}
if (value !== void 0) {
cache.set(prop, value);
return value;
}
const spy = createSpy();
cache.set(prop, spy);
return spy;
}
});
}
mock2.of = of;
const ORIGINAL_PREFIX = "__original_";
const EXCLUDED_PROPS = ["name", "length", "prototype", "_isMockFunction"];
function replace(obj, key, impl) {
if (!obj || typeof key !== "string") return;
const methodKey = String(key);
const originalKey = `${ORIGINAL_PREFIX}${methodKey}`;
const original = obj[key];
if (!(originalKey in obj)) {
obj[originalKey] = original;
}
const spy = createSpy();
spy.mockImplementation(impl);
if (typeof original === "function") {
Object.getOwnPropertyNames(original).forEach((prop) => {
if (!EXCLUDED_PROPS.includes(prop)) {
spy[prop] = original[prop];
}
});
}
obj[key] = spy;
}
mock2.replace = replace;
function restore(obj, key) {
if (!obj) return false;
const restoreMethod = (methodKey) => {
const originalKey = `${ORIGINAL_PREFIX}${methodKey}`;
if (!(originalKey in obj)) return false;
const original = obj[originalKey];
const current = obj[methodKey];
if (current && typeof current === "function") {
Object.getOwnPropertyNames(current).forEach((prop) => {
if (!EXCLUDED_PROPS.includes(prop)) {
try {
original[prop] = current[prop];
} catch (e) {
}
}
});
}
try {
obj[methodKey] = original;
delete obj[originalKey];
return true;
} catch (e) {
return false;
}
};
if (key !== void 0) {
return restoreMethod(String(key));
}
let anyRestored = false;
const keys = Object.getOwnPropertyNames(obj);
for (const k of keys) {
if (k.startsWith(ORIGINAL_PREFIX)) {
const methodKey = k.slice(ORIGINAL_PREFIX.length);
if (restoreMethod(methodKey)) {
anyRestored = true;
}
}
}
return anyRestored;
}
mock2.restore = restore;
function getReplacedMethods(obj) {
if (!obj) return [];
return Object.getOwnPropertyNames(obj).filter((k) => k.startsWith(ORIGINAL_PREFIX)).map((k) => k.slice(ORIGINAL_PREFIX.length)).filter((k) => k && typeof obj[k] !== "undefined");
}
mock2.getReplacedMethods = getReplacedMethods;
function isReplaced(obj, key) {
if (!obj) return false;
const originalKey = `${ORIGINAL_PREFIX}${String(key)}`;
const original = obj[originalKey];
const current = obj[key];
return originalKey in obj && current !== original;
}
mock2.isReplaced = isReplaced;
function verifyRestored(obj, key) {
if (!obj) return false;
const originalKey = `${ORIGINAL_PREFIX}${String(key)}`;
const original = obj[originalKey];
if (!original) return true;
const current = obj[key];
if (current === original) return true;
if (typeof current === "function" && typeof original === "function") {
try {
const currentStr = current.toString().replace(/\s+/g, "");
const originalStr = original.toString().replace(/\s+/g, "");
return currentStr === originalStr;
} catch {
return current === original;
}
}
return false;
}
mock2.verifyRestored = verifyRestored;
function handleStaticMethodInheritance(mockClass, originalClass) {
let currentProto = Object.getPrototypeOf(originalClass);
while (currentProto && currentProto !== Function.prototype) {
Object.getOwnPropertyNames(currentProto).filter((prop) => typeof currentProto[prop] === "function").forEach((methodName) => {
if (!mockClass[methodName]) {
const spy = createSpy();
spy.mockImplementation(currentProto[methodName].bind(mockClass));
mockClass[methodName] = spy;
}
});
currentProto = Object.getPrototypeOf(currentProto);
}
}
function cls(originalClass, options = {}) {
const config = mock2.getConfig();
const { selective = false, implementation = {} } = options;
if (config.debug) {
console.debug("Creating class mock for:", originalClass.name);
}
const createMethodSpy = (method, context) => {
const spy = createSpy();
if (config.trackCalls) {
spy.mockImplementation((...args) => {
if (config.debug) {
console.debug(`Called ${method.name} with:`, args);
}
return method.apply(context, args);
});
} else {
spy.mockImplementation((...args) => method.apply(context, args));
}
return spy;
};
const handleDescriptor = (name, descriptor, context) => {
if (descriptor.get || descriptor.set) {
const spies = createPropertySpy();
const implDescriptor = typeof name === "string" ? Object.getOwnPropertyDescriptor(implementation, name) : void 0;
return {
configurable: true,
enumerable: true,
get: descriptor.get && (implDescriptor?.get ? spies.get?.mockImplementation(implDescriptor.get) : !selective ? spies.get?.mockImplementation(descriptor.get.bind(context)) : descriptor.get.bind(context)),
set: descriptor.set && (implDescriptor?.set ? spies.set?.mockImplementation(implDescriptor.set) : !selective ? spies.set?.mockImplementation(descriptor.set.bind(context)) : descriptor.set.bind(context))
};
}
if (typeof descriptor.value === "function") {
const impl = typeof name === "string" ? implementation[name] : void 0;
if (impl) {
const spy = createSpy();
spy.mockImplementation(impl);
return { ...descriptor, value: spy };
}
if (!selective) {
return {
...descriptor,
value: createMethodSpy(descriptor.value, context)
};
}
}
return {
...descriptor,
value: descriptor.value
};
};
function MockClass(...args) {
if (!(this instanceof MockClass)) {
return new MockClass(...args);
}
const instance = Object.create(originalClass.prototype);
try {
const temp = new originalClass(...args);
Object.assign(instance, temp);
} catch {
}
const processMembers = (target, source) => {
Object.getOwnPropertyNames(source).forEach((name) => {
if (name === "constructor") return;
if (!target.hasOwnProperty(name)) {
const descriptor = Object.getOwnPropertyDescriptor(source, name);
if (descriptor) {
Object.defineProperty(target, name, handleDescriptor(name, descriptor, instance));
}
}
});
};
processMembers(instance, originalClass.prototype);
let proto = Object.getPrototypeOf(originalClass.prototype);
while (proto && proto !== Object.prototype) {
processMembers(instance, proto);
proto = Object.getPrototypeOf(proto);
}
Object.setPrototypeOf(instance, MockClass.prototype);
return instance;
}
MockClass.prototype = Object.create(originalClass.prototype);
MockClass.prototype.constructor = MockClass;
Object.getOwnPropertyNames(originalClass).forEach((name) => {
if (name === "length" || name === "prototype" || name === "name") return;
const descriptor = Object.getOwnPropertyDescriptor(originalClass, name);
if (descriptor) {
Object.defineProperty(MockClass, name, handleDescriptor(name, descriptor, originalClass));
}
});
handleStaticMethodInheritance(MockClass, originalClass);
return MockClass;
}
mock2.cls = cls;
function getConfig() {
return configManager.get();
}
mock2.getConfig = getConfig;
function setConfig(options) {
configManager.set(options);
}
mock2.setConfig = setConfig;
function resetConfig() {
configManager.reset();
}
mock2.resetConfig = resetConfig;
function withConfig(options, fn2) {
return configManager.with(options, fn2);
}
mock2.withConfig = withConfig;
function getConfigStats() {
return configManager.getStats();
}
mock2.getConfigStats = getConfigStats;
})(mock);
// src/types/cache.ts
var MockCache = class {
constructor() {
this.cache = /* @__PURE__ */ new Map();
this.registry = new FinalizationRegistry((key) => {
this.cache.delete(key);
});
}
/**
* Set a value in the cache with optional descriptor
*/
set(key, value, descriptor) {
const entry = {
value,
descriptor,
timestamp: Date.now()
};
if (value && typeof value === "object") {
entry.ref = new WeakRef(value);
this.registry.register(value, String(key));
}
this.cache.set(key, entry);
}
/**
* Get a value from the cache
*/
get(key) {
const entry = this.cache.get(key);
if (!entry) return void 0;
if (entry.ref) {
const value = entry.ref.deref();
if (!value) {
this.cache.delete(key);
return void 0;
}
}
return entry.value;
}
/**
* Get a property descriptor from the cache
*/
getDescriptor(key) {
return this.cache.get(key)?.descriptor;
}
/**
* Check if a key exists in the cache
*/
has(key) {
return this.cache.has(key);
}
/**
* Delete a key from the cache
*/
delete(key) {
this.cache.delete(key);
}
/**
* Clear all entries from the cache
*/
clear() {
this.cache.clear();
}
/**
* Get all keys in the cache
*/
keys() {
return this.cache.keys();
}
};
export { MockCache, MockError, MockErrorCode, UniversalSpy, configManager, createPropertySpy, createSpy, mock, mockConfig, replaceFn, spyOn };
export { createPropertySpy, createSpy };
//# sourceMappingURL=index.js.map
//# sourceMappingURL=index.js.map
{
"name": "@corez/mock",
"version": "0.2.2",
"version": "0.2.4",
"description": "A powerful and flexible TypeScript mocking library for testing",

@@ -52,3 +52,2 @@ "keywords": [

"devDependencies": {
"@jest/globals": "^29.7.0",
"@release-it/conventional-changelog": "^9.0.4",

@@ -55,0 +54,0 @@ "@types/jest": "^29.5.14",

# @corez/mock
A powerful and flexible TypeScript mocking library for testing.
A powerful, flexible, and type-safe mocking library for TypeScript testing.

@@ -10,125 +10,24 @@ [![npm version](https://badge.fury.io/js/@corez/mock.svg)](https://badge.fury.io/js/@corez/mock)

## Table of Contents
- [Features](#features)
- [Installation](#installation)
- [Quick Start](#quick-start)
- [Core Concepts](#core-concepts)
- [API Reference](#api-reference)
- [Advanced Usage](#advanced-usage)
- [Best Practices](#best-practices)
- [Troubleshooting](#troubleshooting)
- [Contributing](#contributing)
- [License](#license)
- [@corez/mock](#corezmock)
- [Table of Contents](#table-of-contents)
- [Core Concepts](#core-concepts)
- [What is Mocking?](#what-is-mocking)
- [Type Safety](#type-safety)
- [Framework Integration](#framework-integration)
- [Installation](#installation)
- [API Reference](#api-reference)
- [Core Functions](#core-functions)
- [mock()](#mock)
- [mock.object()](#mockobject)
- [mock.of()](#mockof)
- [mock.cls()](#mockcls)
- [mock.fn()](#mockfn)
- [mock.partial()](#mockpartial)
- [mock.cast()](#mockcast)
- [mock.replace()](#mockreplace)
- [mock.restore()](#mockrestore)
- [Configuration Methods](#configuration-methods)
- [Best Practices](#best-practices)
- [Testing Patterns](#testing-patterns)
- [Common Pitfalls](#common-pitfalls)
- [Troubleshooting](#troubleshooting)
- [Common Issues](#common-issues)
- [Error Messages](#error-messages)
- [Development](#development)
- [TypeScript Configuration](#typescript-configuration)
- [Contributing](#contributing)
- [Development Guidelines](#development-guidelines)
- [License](#license)
## Features
## Core Concepts
- 🎯 **Type Safety** - Full TypeScript support with precise type inference
- 🔄 **Deep Mocking** - Automatic mocking of nested objects and methods
- 🕵️ **Spy Tracking** - Comprehensive call tracking and verification
- 🎭 **Multiple Mocking Styles** - Support for functions, objects, and classes
- 🔗 **Inheritance Support** - Proper handling of class inheritance and prototype chains
- 🎮 **Intuitive API** - Clean and chainable API design
- 🛡️ **Strict Mode** - Optional strict mode for rigorous testing
- 🔍 **Debug Support** - Detailed logging for troubleshooting
### What is Mocking?
Mocking is a technique used in unit testing to isolate the code being tested by replacing dependencies with controlled
test doubles. This library provides three main types of test doubles:
1. **Mocks**: Complete replacements for dependencies
- Full control over behavior
- Verification capabilities
- Type-safe implementations
2. **Spies**: Wrappers around existing functions
- Track method calls
- Preserve original behavior
- Add verification capabilities
3. **Stubs**: Simple implementations
- Return predefined values
- No verification needed
- Minimal implementation
### Type Safety
The library is built with TypeScript first in mind:
```typescript
interface UserService {
getUser(id: number): Promise<User>;
updateUser(id: number, data: Partial<User>): Promise<void>;
}
const mockService = mock<UserService>();
// ✅ Valid - matches interface
mockService.getUser.mockResolvedValue({id: 1, name: 'User'});
// ❌ Type Error - wrong parameter type
mockService.getUser.mockImplementation((id: string) => Promise.resolve({id: 1}));
// ❌ Type Error - missing required property
mockService.getUser.mockResolvedValue({id: 1});
```
Key type safety features:
- Full interface compliance
- Parameter type checking
- Return type validation
- Generic type support
- Strict null checks
- Method signature matching
### Framework Integration
Works seamlessly with popular testing frameworks:
```typescript
// Jest
describe('UserService', () => {
let mockService: jest.Mocked<UserService>;
beforeEach(() => {
mockService = mock<UserService>();
});
it('should mock async methods', async () => {
mockService.getUser.mockResolvedValue({id: 1, name: 'Test'});
const result = await mockService.getUser(1);
expect(result.name).toBe('Test');
});
});
// Jasmine
describe('UserService', () => {
let mockService: jasmine.SpyObj<UserService>;
beforeEach(() => {
mockService = mock<UserService>();
});
it('should track calls', () => {
mockService.getUser(1);
expect(mockService.getUser).toHaveBeenCalledWith(1);
});
});
```
## Installation

@@ -143,33 +42,16 @@

# Using pnpm (recommended)
# Using pnpm
pnpm add -D @corez/mock
```
## API Reference
## Quick Start
### Core Functions
#### mock()
The primary function that intelligently determines how to create mocks based on the input type:
```typescript
// Function signatures
function mock<T>(): T; // Create from interface
function mock<T>(target: Class<T>): T; // Create from class
function mock<T>(target: T): T; // Create from object
function mock<T>(target?: T, options?: MockOptions<T>): T; // Create with options
import {mock} from '@corez/mock';
interface MockOptions<T> {
selective?: boolean; // Only mock specified methods
preserveThis?: boolean; // Maintain original 'this' context
handleCircular?: boolean; // Handle circular references
implementation?: DeepPartial<T>; // Default implementations
debug?: boolean; // Enable debug logging
trackCalls?: boolean; // Track method calls
preservePrototype?: boolean; // Preserve prototype chain
}
// Mock a function
const greet = mock.fn<(name: string) => string>();
greet.mockImplementation(name => `Hello, ${name}!`);
// Examples:
// 1. Mock from interface
// Mock an object
interface UserService {

@@ -180,34 +62,27 @@ getUser(id: number): Promise<User>;

const mockService = mock<UserService>();
mockService.getUser.mockResolvedValue({id: 1, name: 'Mock'});
mockService.updateUser.mockResolvedValue();
const userService = mock.obj<UserService>(
{
getUser: async id => ({id, name: 'John'}),
updateUser: async user => {},
},
{
overrides: {
getUser: async id => ({id, name: 'Mock User'}),
},
},
);
// 2. Mock from class
class DataService {
private data = new Map<string, any>();
async getData(id: string) {
return this.data.get(id);
// Mock a class
class Database {
async connect() {
/* ... */
}
async query(sql: string) {
/* ... */
}
}
const mockDataService = mock(DataService);
mockDataService.getData.mockResolvedValue({id: '1', data: 'mock'});
// 3. Mock from object
const realService = {
calculate: (x: number, y: number) => x + y,
config: {timeout: 1000},
};
const mockService = mock(realService);
mockService.calculate.mockReturnValue(42);
mockService.config.timeout = 2000;
// 4. Mock with options
const mockWithOptions = mock<UserService>({
selective: true,
preserveThis: true,
implementation: {
getUser: async id => ({id, name: 'Mock'}),
const db = mock.cls(Database, {
overrides: {
query: async () => [{id: 1}],
},

@@ -217,922 +92,334 @@ });

Key features:
## Core Concepts
- Intelligent type inference
- Full TypeScript support
- Automatic spy creation
- Property tracking
- Method call monitoring
- Framework compatibility (Jest/Jasmine)
- Chainable API support
### Mock Functions
#### mock.object()
Create standalone mock functions with full tracking capabilities:
Creates a fully mocked object where all properties and methods are spies:
```typescript
// Function signature
function object<T extends object>(): T;
const mockFn = mock.fn<(x: number) => number>();
// Examples:
interface Service {
getData(): Promise<string>;
processValue(value: string): string;
config: {
timeout: number;
retries: number;
};
}
// Set implementation
mockFn.mockImplementation(x => x * 2);
const mockService = mock.object<Service>();
// Set return value
mockFn.mockReturnValue(42);
// Mock methods
mockService.getData.mockResolvedValue('test data');
mockService.processValue.mockImplementation(value => `processed-${value}`);
// Handle async scenarios
mockFn.mockResolvedValue('result');
mockFn.mockRejectedValue(new Error('failed'));
// Set properties
mockService.config = {
timeout: 1000,
retries: 3,
};
// Use the mock
await mockService.getData(); // Returns 'test data'
mockService.processValue('input'); // Returns 'processed-input'
expect(mockService.getData).toHaveBeenCalled();
expect(mockService.processValue).toHaveBeenCalledWith('input');
// Verify calls
expect(mockFn.calls.count()).toBe(1);
expect(mockFn.calls.all()[0].args).toEqual([1]);
```
Key features:
### Mock Objects
- Creates spies for all methods automatically
- Supports property tracking
- Handles nested objects
- Preserves type information
- Supports getters and setters
- Handles circular references
- Maintains property descriptors
Create mock objects with automatic method tracking:
#### mock.of()
Creates a mock from stubs, supporting both object and array types:
```typescript
// Function signatures
function of<T extends Array<any>>(stubs?: Array<DeepPartial<T[number]>>): MockOf<T>;
function of<T extends ReadonlyArray<any>>(stubs?: ReadonlyArray<DeepPartial<T[number]>>): MockOf<T>;
function of<T extends object>(stubs?: DeepPartial<T>): MockOf<T>;
// Examples:
// 1. Object mocking
interface User {
id: number;
name: string;
getData(): Promise<string>;
getDetails(): {age: number; email: string};
interface UserService {
getUser(id: number): Promise<User>;
updateUser(user: User): Promise<void>;
}
// With initial values
const mockUser = mock.of<User>({
id: 1,
name: 'John',
});
// id and name are preserved
// getData and getDetails are automatically mocked
mockUser.getData.mockResolvedValue('test data');
mockUser.getDetails.mockReturnValue({age: 30, email: 'john@example.com'});
// With method implementation
const mockWithMethod = mock.of<User>({
id: 1,
getData: async () => 'real data',
getDetails: () => ({age: 25, email: 'test@example.com'}),
});
// Method implementations are preserved but monitored
expect(mockWithMethod.getData).toHaveBeenCalled();
// 2. Array mocking
interface Task {
id: number;
title: string;
complete(): Promise<void>;
}
const mockTasks = mock.of<Task[]>([
{id: 1, title: 'Task 1'},
{id: 2, title: 'Task 2'},
]);
// Array items are mocked
mockTasks[0].complete.mockResolvedValue();
mockTasks[1].complete.mockRejectedValue(new Error('Failed'));
// 3. Nested objects
interface ComplexObject {
data: {
id: number;
nested: {
value: string;
};
};
getNestedValue(): string;
}
const mockComplex = mock.of<ComplexObject>({
data: {
id: 1,
nested: {
value: 'test',
const userService = mock.obj<UserService>(
{
getUser: async id => ({id, name: 'John'}),
updateUser: async user => {},
},
{
overrides: {
getUser: async id => ({id, name: 'Mock User'}),
},
},
getNestedValue: function () {
return this.data.nested.value;
},
});
);
// Access call information
const getUserMock = userService.getUser as MockFunction;
console.log(getUserMock.calls.count());
console.log(getUserMock.calls.all());
```
Key features:
### Mock Classes
- Preserves provided values
- Automatically mocks undefined methods
- Supports array mocking
- Handles nested objects
- Maintains type safety
- Monitors method calls
- Supports method chaining
Create mock classes with automatic method tracking:
#### mock.cls()
Creates mock classes with full type safety and spy capabilities:
```typescript
// Function signature
function cls<T extends new (...args: any[]) => any>(classConstructor: T, options?: ClsMockOptions<T>): ClsMock<T>;
interface ClsMockOptions<T extends new (...args: any[]) => any> {
selective?: boolean; // Only mock specified methods
implementation?: DeepPartial<InstanceType<T>>; // Default implementations
}
// Examples:
// 1. Basic class mocking
class UserService {
private value: string;
constructor(initialValue: string = '') {
this.value = initialValue;
class Database {
async connect() {
/* ... */
}
getValue(): string {
return this.value;
async query(sql: string) {
/* ... */
}
setValue(newValue: string): void {
this.value = newValue;
}
static staticMethod(): string {
return 'static';
}
}
// Create mock class
const MockService = mock.cls(UserService);
const instance = new MockService('test');
// Instance methods are spied on
expect(instance.getValue()).toBe('test'); // Original behavior preserved
expect(instance.getValue).toHaveBeenCalled(); // Call tracked
// Static methods are spied on
expect(MockService.staticMethod()).toBe('static'); // Original behavior preserved
expect(MockService.staticMethod).toHaveBeenCalled(); // Call tracked
// 2. Selective mocking
const SelectiveMock = mock.cls(UserService, {
selective: true,
implementation: {
getValue: () => 'mocked',
const MockDatabase = mock.cls(Database, {
overrides: {
query: async () => [{id: 1}],
},
});
const selectiveInstance = new SelectiveMock();
expect(selectiveInstance.getValue()).toBe('mocked'); // Mocked method
expect(selectiveInstance.getValue).toHaveBeenCalled(); // Call tracked
expect(SelectiveMock.staticMethod()).toBe('static'); // Original not mocked
// 3. Inheritance support
class BaseClass {
static baseStatic() {
return 'base';
}
baseMethod() {
return 'base method';
}
}
class DerivedClass extends BaseClass {
static derivedStatic() {
return 'derived';
}
derivedMethod() {
return 'derived method';
}
}
const MockDerived = mock.cls(DerivedClass);
const derivedInstance = new MockDerived();
// Both base and derived methods are mocked
expect(MockDerived.baseStatic()).toBe('base');
expect(MockDerived.derivedStatic()).toBe('derived');
expect(derivedInstance.baseMethod()).toBe('base method');
expect(derivedInstance.derivedMethod()).toBe('derived method');
const db = new MockDatabase();
// Access call information
const queryMock = db.query as MockFunction;
console.log(queryMock.calls.count());
```
Key features:
## API Reference
- Mocks both instance and static methods
- Preserves original behavior by default
- Supports selective mocking
- Handles inheritance chain
- Maintains prototype chain
- Tracks all method calls
- Supports constructor arguments
- Type-safe implementation
### Core APIs
#### mock.fn()
#### `mock.fn<T extends Fn = Fn>(): MockFunction<T>`
Creates mock functions with full spy capabilities:
Creates a mock function with tracking capabilities:
```typescript
// Function signature
function fn<T extends Fn = Fn>(): MockFunction<T>;
const mockFn = mock.fn<(x: number) => number>();
interface MockFunction<T extends Fn = Fn> extends Function {
(...args: Parameters<T>): ReturnType<T>;
mockReturnValue(value: ReturnType<T>): this;
mockResolvedValue<U>(value: U): MockFunction<AsyncFn & {(...args: Parameters<T>): Promise<U>}>;
mockImplementation(fn: T): this;
mockReset(): void;
mockClear(): void;
calls: {
all(): Array<{args: Parameters<T>}>;
count(): number;
};
toHaveBeenCalled(): boolean;
toHaveBeenCalledTimes(n: number): boolean;
toHaveBeenCalledWith(...args: Parameters<T>): boolean;
}
// Set implementation
mockFn.mockImplementation(x => x * 2);
// Examples:
// 1. Basic mock function
const mockFn = mock.fn();
mockFn.mockReturnValue('result');
mockFn('arg1');
expect(mockFn).toHaveBeenCalledWith('arg1');
expect(mockFn.calls.count()).toBe(1);
// Set return value
mockFn.mockReturnValue(42);
// 2. Typed mock function
interface User {
id: number;
name: string;
}
const getUser = mock.fn<(id: number) => Promise<User>>();
getUser.mockResolvedValue({id: 1, name: 'Test User'});
await getUser(1);
expect(getUser).toHaveBeenCalledWith(1);
// Handle async scenarios
mockFn.mockResolvedValue('result');
mockFn.mockRejectedValue(new Error('failed'));
// 3. With implementation
const calculate = mock.fn((x: number, y: number) => x * y);
expect(calculate(2, 3)).toBe(6);
expect(calculate).toHaveBeenCalledWith(2, 3);
// 4. Multiple return values
const multiValue = mock.fn();
multiValue.mockReturnValueOnce(1).mockReturnValueOnce(2).mockReturnValue(3);
expect(multiValue()).toBe(1);
expect(multiValue()).toBe(2);
expect(multiValue()).toBe(3);
// 5. Async function mocking
const fetchData = mock.fn<(query: string) => Promise<any>>();
fetchData.mockResolvedValueOnce({data: 'test'}).mockRejectedValueOnce(new Error('Network error'));
await fetchData('query1'); // Returns { data: 'test' }
await expect(fetchData('query2')).rejects.toThrow('Network error');
// 6. Call tracking
const trackedFn = mock.fn();
trackedFn('a', 1);
trackedFn('b', 2);
expect(trackedFn.calls.count()).toBe(2);
expect(trackedFn.calls.all()).toEqual([{args: ['a', 1]}, {args: ['b', 2]}]);
// Verify calls
expect(mockFn.calls.count()).toBe(1);
expect(mockFn.calls.all()[0].args).toEqual([1]);
```
Key features:
#### `mock.obj<T extends object>(target: T | undefined, options?: ObjMockOptions<T>): MockObject<T>`
- Type-safe mock functions
- Flexible return values
- Async support
- Call tracking
- Chainable API
- Multiple return values
- Error simulation
- Framework compatibility
Creates a mock object with automatic method tracking:
#### mock.partial()
Creates partial mocks from existing objects or class instances:
```typescript
// Function signature
function partial<T extends object>(base: T, stubs?: DeepPartial<T>, options?: PartialOptions<T>): T | PartialBuilder<T>;
interface PartialOptions<T> {
selective?: boolean; // Only mock specified methods
preserveThis?: boolean; // Maintain original 'this' context
handleCircular?: boolean; // Handle circular references
interface UserService {
getUser(id: number): Promise<User>;
updateUser(user: User): Promise<void>;
}
// Examples:
// 1. Basic partial mocking
class UserService {
private value: string = 'original';
getValue(): string {
return this.value;
}
setValue(newValue: string): void {
this.value = newValue;
}
process(data: string): string {
return data.toUpperCase();
}
}
const service = new UserService();
const mockService = mock.partial(service, {
getValue: () => 'mocked',
});
expect(mockService.getValue()).toBe('mocked'); // Mocked method
expect(mockService.getValue).toHaveBeenCalled(); // Call tracked
expect(mockService.process('test')).toBe('TEST'); // Original method
// 2. Selective mocking with preserved context
class DataService {
private data = new Map<string, any>();
constructor(initialData: Record<string, any> = {}) {
Object.entries(initialData).forEach(([key, value]) => {
this.data.set(key, value);
});
}
getData(key: string): any {
return this.data.get(key);
}
setData(key: string, value: any): void {
this.data.set(key, value);
}
}
const dataService = new DataService({key: 'value'});
const mockDataService = mock.partial(
dataService,
const userService = mock.obj<UserService>(
{
getData: function (this: DataService, key: string) {
return this.data.get(key)?.toUpperCase();
},
getUser: async id => ({id, name: 'John'}),
updateUser: async user => {},
},
{
preserveThis: true,
overrides: {
getUser: async id => ({id, name: 'Mock User'}),
},
},
);
mockDataService.setData('key', 'test');
expect(mockDataService.getData('key')).toBe('TEST'); // Uses this context
expect(mockDataService.getData).toHaveBeenCalledWith('key');
// Access call information
const getUserMock = userService.getUser as MockFunction;
console.log(getUserMock.calls.count());
console.log(getUserMock.calls.all());
// 3. Handling circular references
interface Node {
value: string;
next?: Node;
}
// Verify specific calls
expect(getUserMock.calls.count()).toBe(1);
expect(getUserMock.calls.all()[0].args).toEqual([1]);
```
const node: Node = {
value: 'root',
};
node.next = node; // Circular reference
#### `mock.cls<T extends Constructor<any>>(target: T, options?: ClsMockOptions<T>): ClsMock<T>`
const mockNode = mock.partial(
node,
{
value: 'mocked',
},
{
handleCircular: true,
},
);
Creates a mock class with automatic method tracking:
expect(mockNode.value).toBe('mocked');
expect(mockNode.next?.value).toBe('mocked'); // Circular reference handled
// 4. Chainable API
class Service {
getValue() {
return 'original';
```typescript
class Database {
async connect() {
/* ... */
}
transform(value: string) {
return value.toUpperCase();
async query(sql: string) {
/* ... */
}
}
const chainableMock = mock
.partial(new Service())
.spy('getValue')
.preserve('transform')
.with({
getValue: () => 'mocked',
});
const MockDatabase = mock.cls(Database, {
overrides: {
query: async () => [{id: 1}],
},
});
expect(chainableMock.getValue()).toBe('mocked');
expect(chainableMock.getValue).toHaveBeenCalled();
expect(chainableMock.transform('test')).toBe('TEST');
const db = new MockDatabase();
// Access call information
const queryMock = db.query as MockFunction;
console.log(queryMock.calls.count());
// Verify method calls
expect(queryMock.calls.count()).toBe(1);
expect(queryMock.calls.all()[0].args).toEqual(['SELECT * FROM users']);
```
Key features:
#### `mock.compose<T extends Fn>(): MockFunction<T>;`
- Selective method mocking
- Original behavior preservation
- Context preservation
- Circular reference handling
- Property tracking
- Method call monitoring
- Chainable API support
- Type-safe implementation
#### `mock.compose<T extends new (...args: any[]) => any>(target: T, options?: {overrides?: DeepPartial<InstanceType<T>>} & Partial<Config>): ClsMock<T>;`
#### mock.cast()
#### `mock.compose<T extends object>(target: T, options?: {overrides?: DeepPartial<T>; replace?: {[K in keyof T]?: T[K] extends Fn ? Fn : never}} & Partial<Config>): T;`
Casts an existing mock to a different type while preserving its behavior:
#### `mock.compose<T extends object>(partialImpl: DeepPartial<T>, options?: Partial<Config>): T;`
Creates a mock from a class constructor, object, or function:
```typescript
// Function signature
function cast<T, U>(mock: T): U;
// Mock a function
const mockFn = mock.compose<(x: number) => string>();
mockFn.mockImplementation(x => x.toString());
// Examples:
// 1. Basic type casting
interface BaseService {
getData(): string;
// Mock a class
class Database {
async query(sql: string) {
/* ... */
}
}
interface ExtendedService extends BaseService {
getExtendedData(): number;
}
const MockDatabase = mock.compose(Database, {
overrides: {
query: async () => [{id: 1}],
},
});
const baseMock = mock.object<BaseService>();
const extendedMock = mock.cast<BaseService, ExtendedService>(baseMock);
// Original behavior is preserved
baseMock.getData.mockReturnValue('data');
expect(extendedMock.getData()).toBe('data');
// New methods are available
extendedMock.getExtendedData.mockReturnValue(42);
expect(extendedMock.getExtendedData()).toBe(42);
// 2. Complex object casting
interface ComplexType {
nested: {
deep: {
value: number;
};
};
method(): string;
// Mock an object
interface Api {
fetch(url: string): Promise<any>;
}
const partial = {
nested: {
deep: {
value: 42,
const api = mock.compose<Api>(
{
fetch: async url => ({data: []}),
},
{
overrides: {
fetch: async url => ({data: [{id: 1}]}),
},
},
method: () => 'test',
};
);
const typed = mock.cast<ComplexType>(partial);
expect(typed.nested.deep.value).toBe(42);
expect(typed.method()).toBe('test');
// 3. Array type casting
interface Item {
id: number;
name: string;
// Mock with partial implementation
interface ComplexApi {
getUsers(): Promise<User[]>;
getUser(id: number): Promise<User>;
createUser(user: User): Promise<void>;
}
const items = [
{id: 1, name: 'Item 1'},
{id: 2, name: 'Item 2'},
];
const typedItems = mock.cast<Item[]>(items);
expect(typedItems[0].id).toBe(1);
expect(typedItems[1].name).toBe('Item 2');
const partialApi = mock.compose<ComplexApi>({
getUsers: async () => [{id: 1, name: 'John'}],
getUser: async id => ({id, name: 'John'}),
});
```
Key features:
#### `mock.cast<T extends object>(partial: DeepPartial<T>, options?: Partial<Config>): T`
- Type-safe casting
- Behavior preservation
- Property access
- Method call tracking
- Nested object support
- Array support
- Framework compatibility
Casts a partial implementation to a complete mock:
#### mock.replace()
Temporarily replaces a method or property on an object:
```typescript
// Function signature
function replace<T extends object, K extends keyof T>(obj: T, key: K, impl: T[K] & Function): void;
// Examples:
// 1. Basic method replacement
class UserService {
async getUser(id: number) {
// Real implementation
return {id, name: 'Real User'};
}
getValue() {
return 'original';
}
interface CompleteApi {
getUsers(): Promise<User[]>;
getUser(id: number): Promise<User>;
createUser(user: User): Promise<void>;
updateUser(user: User): Promise<void>;
deleteUser(id: number): Promise<void>;
}
const service = new UserService();
// Replace async method
mock.replace(service, 'getUser', async id => {
return {id, name: 'Mock User'};
// Only implement the methods we need
const api = mock.cast<CompleteApi>({
getUsers: async () => [{id: 1, name: 'John'}],
getUser: async id => ({id, name: 'John'}),
});
// Method is replaced and monitored
const user = await service.getUser(1);
expect(user.name).toBe('Mock User');
expect(service.getUser).toHaveBeenCalledWith(1);
// All other methods will be automatically mocked
await api.createUser({id: 1, name: 'Test'}); // Works, returns undefined
await api.updateUser({id: 1, name: 'Test'}); // Works, returns undefined
await api.deleteUser(1); // Works, returns undefined
// 2. Function property handling
const original = function () {
return 'original';
};
(original as any).customProp = 'test';
const obj = {method: original};
mock.replace(obj, 'method', () => 'mocked');
// Original properties are preserved
expect((obj.method as any).customProp).toBe('test');
expect(obj.method()).toBe('mocked');
// 3. Multiple replacements
class Service {
method1() {
return 'original1';
}
method2() {
return 'original2';
}
}
const svc = new Service();
mock.replace(svc, 'method1', () => 'mocked1');
mock.replace(svc, 'method2', () => 'mocked2');
expect(svc.method1()).toBe('mocked1');
expect(svc.method2()).toBe('mocked2');
expect(svc.method1).toHaveBeenCalled();
expect(svc.method2).toHaveBeenCalled();
// 4. Restore original methods
mock.restore(svc, 'method1'); // Restore single method
expect(svc.method1()).toBe('original1');
mock.restore(svc); // Restore all methods
expect(svc.method2()).toBe('original2');
// Access call information
const createUserMock = api.createUser as MockFunction;
expect(createUserMock.calls.count()).toBe(1);
```
Key features:
#### `mock.replace<T extends object, K extends keyof T>(obj: T, key: K, impl: Fn, options?: Partial<Config>): void`
- Temporary method replacement
- Original property preservation
- Method call tracking
- Multiple replacements
- Selective restoration
- Type safety
- Framework compatibility
Replaces methods while preserving original implementation:
#### mock.restore()
Restores replaced methods to their original implementations:
```typescript
// Function signatures
function restore<T extends object>(obj: T): boolean; // Restore all methods
function restore<T extends object, K extends keyof T>(obj: T, key: K): boolean; // Restore specific method
// Examples:
// 1. Basic method restoration
class UserService {
async getUser(id: number) {
return {id, name: 'Real User'};
}
}
const service = new UserService();
mock.replace(service, 'getUser', async id => ({id, name: 'Mock User'}));
// Verify mock works
const mockResult = await service.getUser(1);
expect(mockResult.name).toBe('Mock User');
// Restore original method
const restored = mock.restore(service, 'getUser');
expect(restored).toBe(true); // Method was restored
// Original behavior is restored
const realResult = await service.getUser(1);
expect(realResult.name).toBe('Real User');
// 2. Batch restoration
class Service {
method1() {
return 'original1';
async getData() {
/* ... */
}
method2() {
return 'original2';
}
}
const svc = new Service();
mock.replace(svc, 'method1', () => 'mocked1');
mock.replace(svc, 'method2', () => 'mocked2');
// Get list of replaced methods
const replaced = mock.getReplacedMethods(svc);
expect(replaced).toEqual(['method1', 'method2']);
// Restore all methods
const batchRestored = mock.restore(svc);
expect(batchRestored).toBe(true); // Methods were restored
// 3. Restoration verification
class VerifyService {
getValue() {
return 'original';
async processData(data: any) {
/* ... */
}
}
const verifySvc = new VerifyService();
mock.replace(verifySvc, 'getValue', () => 'mocked');
const service = new Service();
// Check if method is replaced
expect(mock.isReplaced(verifySvc, 'getValue')).toBe(true);
// Replace single method
mock.replace(service, 'getData', async () => ['mocked']);
// Restore and verify
mock.restore(verifySvc, 'getValue');
expect(mock.verifyRestored(verifySvc, 'getValue')).toBe(true);
// Verify the method was replaced
expect(await service.getData()).toEqual(['mocked']);
// 4. Property preservation
class PropService {
method() {
return 'original';
}
}
// Original processData method remains unchanged
expect(service.processData).toBeDefined();
const propSvc = new PropService();
const original = propSvc.method;
(original as any).customProp = 'test';
// Access call information
const getDataMock = service.getData as unknown as MockFunction;
expect(getDataMock.calls.count()).toBe(1);
// Replace and verify custom property is preserved
mock.replace(propSvc, 'method', () => 'mocked');
expect((propSvc.method as any).customProp).toBe('test');
// Restore and verify custom property is still preserved
mock.restore(propSvc, 'method');
expect((propSvc.method as any).customProp).toBe('test');
expect(propSvc.method()).toBe('original');
// 5. Error handling
class InvalidService {
method() {}
}
const invalidSvc = new InvalidService();
// Attempting to restore a method that wasn't replaced
const notRestored = mock.restore(invalidSvc, 'method');
expect(notRestored).toBe(false); // No methods were restored
// Attempting to restore an invalid property
const invalidRestored = mock.restore(invalidSvc, 'nonexistent' as any);
expect(invalidRestored).toBe(false); // No methods were restored
// Restore original implementation
mock.restore(service);
```
Key features:
### Mock Control
- Batch restoration support
- Restoration status tracking
- Property preservation
- Restoration verification
- Method replacement checking
- Graceful error handling
- Type-safe implementation
- Framework compatibility
All mocks provide these control methods:
#### Configuration Methods
The library provides several methods to configure mocking behavior:
```typescript
// Configuration interface
interface Config {
debug: boolean; // Enable debug logging (default: false in production)
trackCalls: boolean; // Track method calls (default: true)
allowUndefined: boolean; // Allow undefined properties (default: true)
strict: boolean; // Throw on undefined properties (default: false)
preservePrototype: boolean; // Preserve prototype chain (default: true)
handleCircular: boolean; // Handle circular references (default: false)
preserveThis: boolean; // Preserve this context (default: false)
selective: boolean; // Only mock specified methods (default: false)
}
// Clear call history
mock.mockClear(); // Clears calls but keeps implementation
// Get current configuration
const config = mock.getConfig();
// Reset completely
mock.mockReset(); // Clears everything
// Update configuration
mock.setConfig({
debug: true, // Enable detailed logging
trackCalls: true, // Track all method calls
allowUndefined: false, // Throw on undefined properties
strict: true, // Strict mode enabled
preservePrototype: true, // Keep prototype chain
handleCircular: true, // Handle circular references
preserveThis: true, // Keep original context
selective: false, // Mock all methods
});
// Restore original
mock.mockRestore(); // Restores original implementation
// Reset to defaults
mock.resetConfig();
// Scoped configuration
mock.withConfig(
{
debug: true,
trackCalls: true,
},
() => {
// Configuration only applies within this function scope
const mockObj = mock.object<Service>();
mockObj.method(); // Will be logged and tracked
},
);
// Configuration scope example with async functions
await mock.withConfig(
{
preserveThis: true,
handleCircular: true,
},
async () => {
const mockService = mock.partial(realService);
await mockService.asyncMethod();
},
);
// Nested configuration scopes
mock.withConfig({debug: true}, () => {
mock.withConfig({strict: true}, () => {
// Both debug and strict are true here
});
// Only debug is true here
});
// Get configuration statistics
interface ConfigStats {
configChanges: number; // Number of times config was changed
activeScopes: number; // Number of active scoped configs
defaultResets: number; // Number of times config was reset
}
const stats = mock.getConfigStats();
// Access state
mock.calls.all(); // Call arguments with context
mock.calls.count(); // Number of calls
```
Configuration options:
## Advanced Usage
- `debug`: Enable debug logging (default: false in production)
- `trackCalls`: Track method calls (default: true)
- `allowUndefined`: Allow undefined properties (default: true)
- `strict`: Throw on undefined properties (default: false)
- `preservePrototype`: Preserve prototype chain (default: true)
- `handleCircular`: Handle circular references (default: false)
- `preserveThis`: Preserve original context (default: false)
- `selective`: Only mock specified methods (default: false)
### Strict Mode
## Best Practices
Enable strict mode for rigorous testing:
### Testing Patterns
1. **Clean State**
- Reset mocks between tests
- Use beforeEach/afterEach hooks
- Avoid state leakage
2. **Type Safety**
- Always provide proper types
- Use interface mocking when possible
- Leverage TypeScript's type inference
3. **Selective Mocking**
- Mock only what you need
- Preserve original behavior when possible
- Use partial mocks for large classes
### Common Pitfalls
1. **Context Loss**
- Use preserveThis option
- Be careful with arrow functions
- Maintain proper binding
2. **Memory Leaks**
- Reset mocks after use
- Clean up spies
- Handle circular references
3. **Circular References**
- Enable handleCircular option for objects with self-references
- Use mock.partial with handleCircular for complex objects
- Consider restructuring deeply nested objects
- Monitor memory usage in tests with circular structures
4. **Type Safety**
- Enable TypeScript strict mode (strict: true in tsconfig.json)
- Use explicit type parameters with mock functions
- Avoid type assertions unless necessary
- Leverage interface mocking for better type inference
## Troubleshooting
### Common Issues
1. **Type Inference Issues**
Problem:
```typescript
const mock = mock<Service>(); // Type 'any' inferred
const strict = mock.obj<Service>({}, {strict: true});
strict.unknownMethod(); // Throws error
```
Solution:
### Async Mocking
```typescript
// Provide explicit type or interface
interface Service {
method(): void;
}
const mockService = mock<Service>();
```
Handle async operations:
2. **This Context Lost**
Problem:
```typescript
class Service {
private value = 'test';
method() {
return this.value;
}
}
const mockService = mock.partial(new Service());
mockService.method(); // this is undefined
```
Solution:
```typescript
const mockService = mock.partial(
new Service(),
const api = mock.obj<Api>(
{},
{
preserveThis: true,
overrides: {
fetch: async url => {
if (url === '/users') {
return [{id: 1}];
}
throw new Error('Not found');
},
},
},

@@ -1142,22 +429,31 @@ );

3. **Circular References**
### Partial Mocking
Problem:
Selectively mock methods:
```typescript
interface Node {
next: Node;
class UserService {
async getUser(id: number) {
/* ... */
}
async validate(user: User) {
/* ... */
}
}
const node = {next: null};
node.next = node;
```
Solution:
const service = new UserService();
```typescript
const mockNode = mock.partial(
node,
// Using replace for method replacement
mock.replace(service, 'getUser', async id => ({
id,
name: 'Mock User',
}));
// Using overrides for partial implementation
const partialService = mock.obj<UserService>(
{},
{
handleCircular: true,
overrides: {
getUser: async id => ({id, name: 'Mock User'}),
},
},

@@ -1167,147 +463,63 @@ );

### Error Messages
## Best Practices
Common error messages and their solutions:
1. **Reset Between Tests**
1. **"Cannot spy on property which has no getter"**
```typescript
beforeEach(() => {
mockFn.mockReset();
// or
mockObj.mockReset();
});
```
- Ensure the property exists on the target object
- Check if the property is accessible
- Use proper access modifiers
2. **Type Safety**
2. **"Cannot mock non-function value"**
```typescript
// Prefer interfaces for better type inference
interface Service {
method(): string;
}
const mock = mock.obj<Service>();
```
- Verify the target is actually a function
- Check if the property is a getter/setter
- Ensure proper type definitions
3. **Error Handling**
3. **"Maximum call stack size exceeded"**
```typescript
// Always test error cases
api.fetch.mockRejectedValue(new Error('Network error'));
await expect(api.fetch()).rejects.toThrow('Network error');
```
- Check for circular references
- Enable handleCircular option
- Review recursive mock implementations
- Consider using mock.partial with handleCircular option
4. **Verification**
```typescript
// Verify call count and arguments
expect(mockFn.calls.count()).toBe(1);
expect(mockFn.calls.all()[0].args).toEqual(['expected arg']);
```
4. **"Property has a circular reference"**
## Troubleshooting
- Enable handleCircular in mock options
- Use mock.partial with handleCircular: true
- Consider restructuring the object graph
- Use WeakMap for circular reference tracking
### Common Issues
5. **"Accessing undefined property"**
1. **Mock Not Tracking Calls**
- Check if strict mode is enabled
- Verify property exists on mock object
- Consider using allowUndefined option
- Add explicit property definitions
- Enable `trackCalls` in config
- Ensure mock is properly created
6. **"Invalid spy implementation"**
- Ensure mock implementation matches original signature
- Check for proper this context binding
- Verify async/sync function compatibility
- Review method parameter types
2. **Type Inference Issues**
## Development
- Use explicit type parameters
- Define interfaces for complex types
```bash
# Install dependencies
pnpm install
3. **Prototype Chain Issues**
- Enable `preservePrototype`
- Use `mock.cls()` for classes
# Run tests
pnpm test
# Build
pnpm build
# Lint
pnpm lint
# Format
pnpm format
# Run tests with coverage
pnpm test:coverage
```
For development, make sure to:
1. Write tests for new features
2. Update documentation for API changes
3. Follow the TypeScript coding style
4. Run the full test suite before submitting PR
### TypeScript Configuration
The library is built with strict TypeScript settings:
```json
{
"compilerOptions": {
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"strictBindCallApply": true,
"strictPropertyInitialization": true,
"noImplicitThis": true,
"useUnknownInCatchVariables": true,
"alwaysStrict": true,
"noUncheckedIndexedAccess": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"exactOptionalPropertyTypes": true
}
}
```
These strict settings ensure:
- No implicit any types
- Null and undefined checks
- Strict function type checking
- Proper this context handling
- Strict property initialization
- Index signature checking
- Exhaustive switch/case handling
## Contributing
1. Fork the repository
2. Create your feature branch (`git checkout -b feature/amazing-feature`)
3. Commit your changes (`git commit -m 'Add some amazing feature'`)
4. Push to the branch (`git push origin feature/amazing-feature`)
5. Open a Pull Request at https://github.com/corezlab/mock
We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for details.
Please ensure your PR:
- Includes tests for new features
- Updates documentation as needed
- Follows the existing code style
- Includes a clear description of changes
### Development Guidelines
1. **Code Style**
- Follow TypeScript best practices
- Use ESLint and Prettier
- Write clear comments
- Use meaningful variable names
2. **Testing**
- Write unit tests for new features
- Maintain test coverage
- Test edge cases
- Use meaningful test descriptions
3. **Documentation**
- Update README.md as needed
- Document new features
- Include examples
- Keep API documentation up to date
## License
This project is licensed under the Apache License 2.0 - see the [LICENSE](LICENSE) file for details.
Apache-2.0 - see [LICENSE](LICENSE) for details.
/**
* Configuration management for the mocking system
* Mock configuration options
*/
/**
* Global configuration options
*/
export interface Config {
/** Whether to enable debug mode */
debug: boolean;
/** Whether to track method calls by default */
trackCalls: boolean;
/** Whether to allow undefined properties */
allowUndefined: boolean;
/** Whether to use strict mode */
strict: boolean;
/** Default timeout for async operations (ms) */
timeout: number;
/** Whether to preserve prototype chain */
preservePrototype: boolean;
}
/**
* Default configuration values
*/
const defaultConfig: Config = {
debug: process.env.NODE_ENV === 'development',
trackCalls: true,
allowUndefined: true,
strict: false,
timeout: 5000,
preservePrototype: true,
};
/**
* Configuration value types
*/
type ConfigValue<K extends keyof Config> = Config[K];
/**
* Type guard for configuration values
*/
function isValidConfigValue<K extends keyof Config>(key: K, value: unknown): value is ConfigValue<K> {
switch (key) {
case 'debug':
case 'trackCalls':
case 'allowUndefined':
case 'strict':
case 'preservePrototype':
return typeof value === 'boolean';
case 'timeout':
return typeof value === 'number' && value > 0;
default:
return false;
}
}
/**
* Configuration update type
*/
type ConfigUpdate = Partial<Config>;
/**
* Configuration statistics
*/
export interface ConfigStats {
updateCount: number;
lastUpdate: number;
cacheHits: number;
}
/**
* Global configuration state
*/
class GlobalConfig {
private config: Config;
private cachedConfig: Readonly<Config> | null = null;
private lastUpdate: number = 0;
private updateCount: number = 0;
private cacheHits: number = 0;
private readonly cacheTimeout: number = 1000; // 1 second cache timeout
private static instance: GlobalConfig;
private constructor() {
this.config = {...defaultConfig};
}
/**
* Gets the singleton instance
* Whether to allow accessing undefined properties
* Used in object mocking to control property access
* @default false
*/
static getInstance(): GlobalConfig {
if (!GlobalConfig.instance) {
GlobalConfig.instance = new GlobalConfig();
}
return GlobalConfig.instance;
}
allowUndefined: boolean;
/**
* Gets the current configuration
* Whether to use strict mode
* In strict mode, adding new properties to mock objects is not allowed
* @default false
*/
getConfig(): Readonly<Config> {
if (this.cachedConfig && Date.now() - this.lastUpdate < this.cacheTimeout) {
this.cacheHits++;
return this.cachedConfig;
}
strict: boolean;
this.cachedConfig = Object.freeze({...this.config});
return this.cachedConfig;
}
/**
* Updates the configuration
* Whether to track method calls
* When enabled, creates a MethodTracker to record call history
* @default false
*/
updateConfig(options: ConfigUpdate): void {
const newConfig = {...this.config};
let hasChanges = false;
trackCalls: boolean;
type Key = keyof typeof options;
(Object.keys(options) as Key[]).forEach(key => {
if (key in defaultConfig && isValidConfigValue(key, options[key])) {
if (this.config[key] !== options[key]) {
(newConfig[key] as any) = options[key];
hasChanges = true;
}
}
});
if (hasChanges) {
this.config = newConfig;
this.invalidateCache();
this.updateCount++;
}
}
/**
* Resets configuration to defaults
* Whether to enable debug mode
* When enabled, outputs debug logs for mock creation and interactions
* @default false
*/
resetConfig(): void {
const newConfig = {
...defaultConfig,
debug: process.env.NODE_ENV === 'development',
};
if (this.hasConfigChanged(newConfig)) {
this.config = newConfig;
this.invalidateCache();
this.updateCount++;
}
}
debug: boolean;
/**
* Gets update statistics
* Default timeout for async operations in milliseconds
* Used in async mocks, promise wrappers, and retry mechanisms
* @default 5000
*/
getStats(): ConfigStats {
return {
updateCount: this.updateCount,
lastUpdate: this.lastUpdate,
cacheHits: this.cacheHits,
};
}
timeout: number;
/**
* Creates a scoped configuration
* Whether to preserve the prototype chain
* @default true
*/
withConfig<T>(options: ConfigUpdate, fn: () => T): T {
const previous = {...this.getConfig()};
try {
this.updateConfig(options);
return fn();
} finally {
this.updateConfig(previous);
}
}
/**
* Checks if configuration has changed
*/
private hasConfigChanged(newConfig: Partial<Config>): boolean {
return Object.entries(newConfig).some(([key, value]) => {
const configKey = key as keyof Config;
return this.config[configKey] !== value;
});
}
/**
* Invalidates the configuration cache
*/
private invalidateCache(): void {
this.cachedConfig = null;
this.lastUpdate = Date.now();
}
preservePrototype: boolean;
}
/**
* Global configuration API
* Similar to Jest's global config API
* Default configuration values
*/
export const configManager = {
/**
* Gets current configuration
*/
get(): Readonly<Config> {
return GlobalConfig.getInstance().getConfig();
},
/**
* Updates configuration
*/
set(options: ConfigUpdate): void {
GlobalConfig.getInstance().updateConfig(options);
},
/**
* Resets configuration to defaults
*/
reset(): void {
GlobalConfig.getInstance().resetConfig();
},
/**
* Creates a scoped configuration
*/
with<T>(options: ConfigUpdate, fn: () => T): T {
return GlobalConfig.getInstance().withConfig(options, fn);
},
/**
* Gets configuration statistics
*/
getStats(): ConfigStats {
return GlobalConfig.getInstance().getStats();
},
export const DEFAULT_CONFIG: Config = {
allowUndefined: false,
strict: false,
trackCalls: false,
debug: false,
timeout: 5000,
preservePrototype: true,
};
// For backward compatibility
export const mockConfig = configManager;

@@ -18,2 +18,3 @@ /**

PERFORMANCE_CONSTRAINT_VIOLATED = 'PERF_VIOLATED',
UNDEFINED_METHOD = 'UNDEFINED_METHOD',
}

@@ -20,0 +21,0 @@

@@ -1,5 +0,2 @@

export * from './config';
export * from './errors';
export * from './mock';
export * from './spy';
export * from './types';
export {createPropertySpy, createSpy} from './spy';
export type {MockFunction} from './types';

@@ -1,1066 +0,154 @@

import {Config, configManager, ConfigStats} from './config';
import {createPropertySpy, createSpy, replaceFn} from './spy';
import {ClsMock, ClsMockOptions, DeepPartial, Fn, MockFunction, MockOf, PartialBuilder, PartialOptions} from './types';
import {Config, DEFAULT_CONFIG} from './config';
import {cast, cls, compose, fn, obj, replace} from './mocks';
import {MockRegistry} from './registry';
import {
ClsMock,
ClsMockOptions,
Constructor,
DeepPartial,
Fn,
Mock,
MockFunction,
MockObject,
ObjMockOptions,
} from './types';
/**
* Universal mock function that provides a unified entry point for mocking objects and classes.
* It automatically detects the input type and applies the appropriate mocking strategy.
*
* @template T -The type to mock
* @param target -The target to mock (class constructor, object instance, or partial implementation)
* @param options -Optional configuration for mocking behavior
* @returns A mocked version of the target
* @throws {TypeError} When the target type cannot be determined
* @throws {Error} When circular references are detected without proper handling
*
* @remarks
* This function serves as the main entry point for all mocking operations.
* It intelligently determines the type of input and applies the most appropriate mocking strategy:
* -Class mocking: Creates a mock class with spied methods
* -Object mocking: Creates a proxy-based mock with tracked properties
* -Partial mocking: Allows selective method/property mocking
* -Interface mocking: Creates complete mock from type information
*
* @example
* ```typescript
* // Mock a class
* class Service {
* getData() { return 'data'; }
* }
* const MockService = mock(Service);
* const instance = new MockService();
*
* // Mock a class with options
* const MockService = mock(Service, {
* selective: true,
* implementation: {
* getData: () => 'mocked'
* }
* });
*
* // Mock an object
* interface User {
* id: number;
* getName(): string;
* }
* const mockUser = mock<User>({
* id: 1,
* getName: () => 'John'
* });
*
* // Create a complete mock from interface
* const mockUser = mock<User>();
*
* // Partial mock of existing instance
* const realService = new Service();
* const mockService = mock(realService, {
* getData: () => 'mocked'
* });
* ```
*
* @see {@link mock.object} For creating complete mock objects
* @see {@link mock.partial} For creating partial mocks
* @see {@link mock.cls} For creating mock classes
* @see {@link mock.of} For creating mocks from stubs
* Mock implementation with configuration management and registry capabilities.
* Provides a unified interface for creating and managing mocks with shared configuration.
*/
export function mock<T extends new (...args: any[]) => any>(target: T, options?: ClsMockOptions<T>): ClsMock<T>;
export function mock<T extends object>(target: T, implementation?: DeepPartial<T>): T;
export function mock<T extends object>(partialImpl?: DeepPartial<T>): MockOf<T>;
export function mock<T extends object>(
targetOrImpl?: T | DeepPartial<T> | (new (...args: any[]) => any),
optionsOrImpl?: ClsMockOptions<any> | DeepPartial<T>,
): T | ClsMock<any> | MockOf<T> {
// Merge configuration
const mockOptions = optionsOrImpl && 'trackCalls' in optionsOrImpl ? (optionsOrImpl as ClsMockOptions<any>) : {};
const currentConfig: Config = {
...mock.getConfig(),
...mockOptions,
};
class MockImpl extends MockRegistry implements Mock {
private currentConfig: Config;
if (currentConfig.debug) {
console.debug('Mocking:', targetOrImpl);
constructor(config: Partial<Config> = {}) {
super();
this.currentConfig = {...DEFAULT_CONFIG, ...config};
}
// Case 1: No arguments -create complete mock object
if (!targetOrImpl) {
return mock.object<T>();
}
// Case 2: Target is a class constructor
if (typeof targetOrImpl === 'function' && targetOrImpl.prototype) {
return mock.cls(targetOrImpl as new (...args: any[]) => any, optionsOrImpl as ClsMockOptions<any>);
}
// Case 3: Target is an existing instance
if (targetOrImpl && typeof targetOrImpl === 'object' && optionsOrImpl) {
return mock.partial(targetOrImpl as T).with(optionsOrImpl as DeepPartial<T>);
}
// Case 4: Partial implementation provided
return mock.of<T>(targetOrImpl as DeepPartial<T>);
}
/**
* Mock utilities namespace that provides a comprehensive set of mocking tools
* for testing TypeScript/JavaScript applications.
*
* @namespace mock
* @description
* A comprehensive mocking framework that provides tools for:
* -Function mocking with spy capabilities
* -Object mocking with property tracking
* -Class mocking with inheritance support
* -Partial mocking for selective member overrides
* -Framework compatibility (Jest/Jasmine)
*
* The namespace provides a unified API for all mocking operations while maintaining
* type safety and providing detailed tracking and verification capabilities.
*/
export namespace mock {
/**
* Creates a spy function that can track calls and mock implementations.
* The spy function maintains the same type signature as the original function.
*
* @template T -The function type to mock
* @returns {MockFunction<T>} A mock function with spy capabilities
*
* @remarks
* Spy functions provide comprehensive tracking and control over function calls:
* -Call count tracking
* -Arguments validation
* -Return value mocking
* -Implementation replacement
* -Asynchronous behavior simulation
*
* The spy maintains the original function's type information while adding
* additional methods for tracking and controlling behavior.
*
* @example
* ```typescript
* // Create a typed spy
* const mockFn = mock.fn<(x: number) => string>();
*
* // Configure behavior
* mockFn.mockReturnValue('mocked');
* mockFn.mockImplementation(x => x.toString());
*
* // Use in tests
* mockFn(42);
* expect(mockFn).toHaveBeenCalledWith(42);
* expect(mockFn.calls.count()).toBe(1);
* ```
*
* @see {@link MockFunction} For the complete spy API
* Get or set mock configuration
* @param config Optional configuration to update
* @returns Current configuration when called without arguments
*/
export function fn<T extends Fn = Fn>(): MockFunction<T> {
return createSpy<T>();
}
/**
* Creates a fully mocked object where all properties and methods are spies.
* The mock object maintains the same type information as the original.
*
* @template T -The object type to mock
* @returns {T} A mock object with all members as spies
*
* @remarks
* Object mocking provides comprehensive control over object behavior:
* -Automatic spy creation for methods
* -Property value tracking
* -Getter/setter interception
* -Nested object handling
* -Circular reference protection
*
* The mock maintains the original object's type information while adding
* spy capabilities to all methods and tracking for all properties.
*
* Features:
* -Auto-creates spies for all methods
* -Supports getters and setters
* -Maintains property descriptors
* -Handles nested objects
* -Preserves type safety
*
* @example
* ```typescript
* interface Service {
* getData(): Promise<string>;
* value: number;
* }
*
* const mockService = mock.object<Service>();
* mockService.getData.mockResolvedValue('data');
* mockService.value = 42;
*
* await mockService.getData();
* expect(mockService.getData).toHaveBeenCalled();
* ```
*
* @see {@link mock.partial} For creating partial mocks
* @see {@link mock.of} For creating mocks from stubs
*/
export function object<T extends object>(): T {
const target = {} as T;
const cache = new Map<string | symbol, any>();
const descriptors = new Map<string | symbol, PropertyDescriptor>();
const refs = new WeakMap();
const config = mock.getConfig();
const handler: ProxyHandler<T> = {
get: (_, prop: string | symbol) => {
if (prop === 'then') {
return undefined;
}
const descriptor = descriptors.get(prop);
if (descriptor) {
return descriptor.get?.();
}
if (!cache.has(prop)) {
if (!config.allowUndefined && !prop.toString().startsWith('__')) {
if (config.strict) {
throw new Error(`Accessing undefined property: ${String(prop)}`);
}
if (config.debug) {
console.warn(`Accessing undefined property: ${String(prop)}`);
}
}
const spy = createSpy();
if (config.trackCalls) {
spy.mockImplementation(function (...args: any[]) {
if (config.debug) {
console.debug(`Called ${String(prop)} with:`, args);
}
return undefined;
});
}
cache.set(prop, spy);
return spy;
}
return cache.get(prop);
},
set: (_, prop: string | symbol, value: any) => {
// If value is an object, check for circular references
if (value && typeof value === 'object') {
if (refs.has(value)) {
// If a reference already exists, use the original value directly
cache.set(prop, value);
return true;
}
refs.set(value, true);
}
cache.delete(prop);
cache.set(prop, value);
return true;
},
defineProperty: (_, prop: string | symbol, descriptor: PropertyDescriptor) => {
descriptors.set(prop, {
...descriptor,
configurable: true,
enumerable: true,
});
return true;
},
getOwnPropertyDescriptor: (_, prop: string | symbol) => {
const descriptor = descriptors.get(prop);
if (descriptor) {
return {
...descriptor,
configurable: true,
enumerable: true,
};
}
if (cache.has(prop)) {
return {
value: cache.get(prop),
writable: true,
configurable: true,
enumerable: true,
};
}
return undefined;
},
has: (_, prop: string | symbol) => {
return descriptors.has(prop) || cache.has(prop);
},
ownKeys: () => {
return [...descriptors.keys(), ...cache.keys()];
},
};
return new Proxy(target, handler);
}
/**
* Creates a partial mock from an existing object, allowing selective mocking of members.
* This is useful when you want to mock only specific parts of an object while keeping
* the rest of the functionality intact.
*
* @template T -The object type to partially mock
* @param base -The original object to create a partial mock from
* @param stubs -Optional stubs to initialize the mock with
* @param options -Configuration options for the partial mock
* @returns {T | PartialBuilder<T>} A mock object with partial mocking or a builder for further configuration
*
* @remarks
* Partial mocking provides fine-grained control over which members to mock:
* -Selective method mocking
* -Original behavior preservation
* -Prototype chain maintenance
* -Nested object support
* -Circular reference handling
*
* Features:
* -Selective member mocking
* -Preserves original behavior
* -Maintains prototype chain
* -Supports nested objects
* -Type-safe implementation
*
* @example
* ```typescript
* class Service {
* getData() { return 'real data'; }
* process() { return this.getData().toUpperCase(); }
* }
*
* // Method 1: Using with()
* const mockService1 = mock.partial(new Service()).with({
* getData: () => 'mock data'
* });
*
* // Method 2: Direct two parameters
* const mockService2 = mock.partial(new Service(), {
* getData: () => 'mock data'
* });
*
* expect(mockService1.process()).toBe('MOCK DATA');
* expect(mockService2.process()).toBe('MOCK DATA');
* ```
*
* @throws {Error} When circular references are detected without proper handling
* @see {@link PartialBuilder} For the builder pattern API
* @see {@link PartialOptions} For available configuration options
*/
export function partial<T extends object>(base: T, stubs: DeepPartial<T>, options?: PartialOptions<T>): T;
export function partial<T extends object>(base: T): PartialBuilder<T>;
export function partial<T extends object>(
base: T,
stubs?: DeepPartial<T>,
options?: PartialOptions<T>,
): T | PartialBuilder<T> {
const config = mock.getConfig();
const defaultOptions: Required<PartialOptions<T>> = {
selective: false,
preserveThis: config.preservePrototype,
autoSpy: config.trackCalls,
handleCircular: false,
};
const spyMethods = new Set<keyof T>();
const preserveProps = new Set<keyof T>();
function createMock(withStubs: DeepPartial<T> = {} as DeepPartial<T>, withOptions: PartialOptions<T> = {}): T {
const finalOptions = {...defaultOptions, ...withOptions};
let currentKey = '';
if (config.debug) {
console.debug('Creating partial mock for:', base.constructor.name);
}
// Create a new object with the same prototype chain
const result = Object.create(finalOptions.preserveThis ? Object.getPrototypeOf(base) : null) as T;
// First pass: copy all properties to handle circular references
Object.getOwnPropertyNames(base).forEach(key => {
const typedKey = key as keyof T;
if (!Object.prototype.hasOwnProperty.call(withStubs, key) && !spyMethods.has(typedKey)) {
if (preserveProps.has(typedKey)) {
result[typedKey] = base[typedKey];
} else {
const descriptor = Object.getOwnPropertyDescriptor(base, key)!;
if (typeof descriptor.value === 'function' && !finalOptions.selective) {
const spy = createSpy();
spy.mockImplementation(descriptor.value.bind(result));
result[typedKey] = spy as any;
} else {
Object.defineProperty(result, key, descriptor);
}
}
}
});
// Second pass: apply stubs and handle spies
try {
if (finalOptions.handleCircular) {
const merged = {...base, ...withStubs} as T;
Object.assign(result, merged);
// Handle circular references
for (const key in merged) {
const value = (merged as any)[key];
if (value === base) {
(result as any)[key] = result;
}
}
} else {
Object.keys(withStubs).forEach(key => {
const typedKey = key as keyof T;
if (preserveProps.has(typedKey)) {
result[typedKey] = base[typedKey];
return;
}
const value = (withStubs as any)[key];
if (typeof value === 'function') {
const spy = createSpy();
spy.mockImplementation(finalOptions.preserveThis ? value.bind(result) : value);
result[typedKey] = spy as any;
} else if (value && typeof value === 'object' && !Array.isArray(value)) {
// Check for circular references
if (value === base && !finalOptions.handleCircular) {
throw new Error(`Property ${String(typedKey)} has a circular reference`);
}
try {
result[typedKey] = {...(base as any)[typedKey], ...value};
} catch (e) {
if (e instanceof RangeError) {
throw new Error(
`Property ${String(typedKey)} has a circular reference.\nConsider using handleCircular: true option.`,
);
}
throw e;
}
} else {
result[typedKey] = value;
}
});
}
} catch (e) {
if (e instanceof RangeError && finalOptions.handleCircular) {
return mock.cast<T>({...base, ...withStubs});
}
throw e;
}
// Third pass: handle additional spy methods
spyMethods.forEach(method => {
if (typeof result[method] === 'function' && !preserveProps.has(method)) {
const original = result[method] as Function;
const spy = createSpy();
spy.mockImplementation(finalOptions.preserveThis ? original.bind(result) : original);
result[method] = spy as any;
}
});
return result;
configure(): Config;
configure(config: Partial<Config>): void;
configure(config?: Partial<Config>): Config | void {
if (!config) {
return {...this.currentConfig};
}
const builder: PartialBuilder<T> = {
with: (withStubs?: DeepPartial<T>, withOptions?: PartialOptions<T>) => createMock(withStubs, withOptions),
spy: (...methods: (keyof T)[]) => {
methods.forEach(method => spyMethods.add(method));
return builder;
},
preserve: (...properties: (keyof T)[]) => {
properties.forEach(prop => preserveProps.add(prop));
return builder;
},
};
return stubs ? createMock(stubs, options) : builder;
Object.assign(this.currentConfig, config);
}
/**
* Casts a partial object to its full type, useful for type coercion in mocks
* @template T -The target type to cast to
* @param partial -The partial object to cast
* @returns The partial object cast to type T
* @example
* ```typescript
* interface Complex {
* nested: { value: number };
* }
* const partial = { nested: { value: 42 } };
* const typed = mock.cast<Complex>(partial);
* ```
* Create a mock function with tracking capabilities
* @param options Optional configuration overrides
* @returns Mocked function with spy features
*/
export function cast<T extends object>(partial: DeepPartial<T>): T {
const proxies = new WeakMap(); // Used to cache created proxy objects
const handler: ProxyHandler<any> = {
get: (target, prop) => {
if (prop === 'then') {
return undefined;
}
if (prop in target) {
const value = target[prop];
if (value && typeof value === 'object') {
// If a proxy has been created for this object, return the cached proxy directly.
if (proxies.has(value)) {
return proxies.get(value);
}
// Create new proxy and cache
const proxy = new Proxy(value, handler);
proxies.set(value, proxy);
return proxy;
}
return value;
}
return undefined;
},
};
const proxy = new Proxy(partial, handler);
if (partial && typeof partial === 'object') {
proxies.set(partial, proxy);
}
return proxy as T;
fn<T extends Fn>(options?: Partial<Config>): MockFunction<T> {
const mockFn = fn<T>();
this.registerMock(mockFn, 'function');
return mockFn;
}
/**
* Creates a mock from stubs, supporting both object and array types.
* Preserves provided values while automatically mocking undefined members.
*
* @template T -The type to create a mock from
* @param stubs -Optional stubs to initialize the mock with
* @returns {MockOf<T>} A mock object or array with all methods as MockFunction
*
* @remarks
* Mock creation from stubs provides flexible initialization:
* -Value preservation
* -Automatic method mocking
* -Array support
* -Nested object handling
* -Type inference
*
* Features:
* -Preserves provided property values
* -Preserves and monitors provided method implementations
* -Automatically mocks undefined methods
* -Handles nested objects and arrays
* -Maintains type safety
*
* @example
* ```typescript
* interface User {
* id: number;
* name: string;
* getData(): string;
* }
*
* // With initial values
* const mockUser = mock.of<User>({
* id: 1,
* name: 'John'
* });
* // id and name are preserved
* // getData is automatically mocked
* mockUser.getData.mockReturnValue('data');
*
* // With method implementation
* const mockWithMethod = mock.of<User>({
* id: 1,
* getData: () => 'data'
* });
* // getData implementation is preserved but monitored
* mockWithMethod.getData.mockReturnValue('new data');
* ```
*
* @throws {Error} When circular references are detected
* @see {@link MockOf} For the resulting mock type
* Create a mock object with tracking capabilities
* @param target Original object to mock
* @param options Mock configuration and implementation
* @returns Mocked object with spy features
*/
export function of<T extends Array<any>>(stubs?: Array<DeepPartial<T[number]>>): MockOf<T>;
export function of<T extends ReadonlyArray<any>>(stubs?: ReadonlyArray<DeepPartial<T[number]>>): MockOf<T>;
export function of<T extends object>(stubs?: DeepPartial<T>): MockOf<T>;
export function of<T extends object>(stubs: any = {} as T): MockOf<T> {
if (Array.isArray(stubs)) {
return stubs.map(item => of(item)) as MockOf<T>;
}
const base = object<T>();
const result = Object.create(Object.getPrototypeOf(base));
const cache = new Map<string | symbol, any>();
// Copy properties from stubs
Object.entries(stubs).forEach(([key, value]) => {
result[key] = value;
});
return new Proxy(result, {
get(target, prop) {
if (prop === 'then') return undefined;
// Check cache first
if (cache.has(prop)) {
return cache.get(prop);
}
// Get value from target
const value = target[prop];
// If it's a function, create a spy
if (typeof value === 'function') {
const spy = createSpy();
spy.mockImplementation(value);
cache.set(prop, spy);
return spy;
}
// Return existing value or create spy for undefined methods
if (value !== undefined) {
cache.set(prop, value);
return value;
}
const spy = createSpy();
cache.set(prop, spy);
return spy;
},
}) as MockOf<T>;
obj<T extends object>(target: T | undefined, options: ObjMockOptions<T> = {}): MockObject<T> {
const mockObj = obj(target, {...this.currentConfig, ...options});
this.registerMock(mockObj, 'object', target);
return mockObj;
}
/**
* Copies function properties from source to target
* @internal
* Create a mock class with tracking capabilities
* @param target Original class to mock
* @param options Mock configuration and implementation
* @returns Mocked class constructor
*/
function copyFunctionProperties(source: any, target: any): void {
if (typeof source === 'function') {
Object.getOwnPropertyNames(source).forEach(prop => {
if (prop !== 'name' && prop !== 'length' && prop !== 'prototype') {
target[prop] = source[prop];
}
});
}
cls<T extends Constructor<any>>(target: T, options?: ClsMockOptions<T>): ClsMock<T> {
const mockCls = cls(target, {...this.currentConfig, ...options});
this.registerMock(mockCls as unknown as T, 'class', target);
return mockCls;
}
const ORIGINAL_PREFIX = '__original_';
const EXCLUDED_PROPS = ['name', 'length', 'prototype', '_isMockFunction'];
/**
* Replaces a method with mock implementation while preserving original properties
* @template T -The object type containing the method
* @template K -The key of the method to replace
* @param obj -The object containing the method
* @param key -The key of the method to replace
* @param impl -The mock implementation
* @example
* ```typescript
* const obj = { method: () => 'original' };
* mock.replace(obj, 'method', () => 'mocked');
* ```
* Create a mock from partial implementation
* @param partial Partial implementation to mock
* @param options Mock configuration
* @returns Complete mock object
*/
export function replace<T extends object, K extends keyof T>(obj: T, key: K, impl: Fn): void {
if (!obj || typeof key !== 'string') return;
const methodKey = String(key);
const originalKey = `${ORIGINAL_PREFIX}${methodKey}`;
const original = obj[key];
// Store original method for restoration
if (!(originalKey in obj)) {
(obj as any)[originalKey] = original;
}
// Create spy with implementation
const spy = createSpy();
spy.mockImplementation(impl);
// Copy properties from original function to spy
if (typeof original === 'function') {
Object.getOwnPropertyNames(original).forEach(prop => {
if (!EXCLUDED_PROPS.includes(prop)) {
(spy as any)[prop] = (original as any)[prop];
}
});
}
// Replace the method with spy
obj[key] = spy as any;
cast<T extends object>(partial: DeepPartial<T>, options?: Partial<Config>): T {
const mockObj = cast(partial, {...this.currentConfig, ...options});
this.registerMock(mockObj, 'object', partial as T);
return mockObj;
}
/**
* Restores replaced methods to their original implementations
* @template T -The object type containing the method
* @template K -The key of the method to restore
* @param obj -The object containing the method
* @param key -Optional key of the specific method to restore
* @returns {boolean} Whether any methods were restored
* @example
* ```typescript
* // Restore specific method
* mock.restore(service, 'method');
*
* // Restore all replaced methods
* mock.restore(service);
* ```
* Create a composed mock from target
* @param target Class or object to compose mock from
* @param options Mock configuration and implementation
*/
export function restore<T extends object>(obj: T): boolean;
export function restore<T extends object, K extends keyof T>(obj: T, key: K): boolean;
export function restore<T extends object, K extends keyof T>(obj: T, key?: K): boolean {
if (!obj) return false;
// Helper to restore a single method
const restoreMethod = (methodKey: string): boolean => {
const originalKey = `${ORIGINAL_PREFIX}${methodKey}`;
if (!(originalKey in obj)) return false;
const original = (obj as any)[originalKey];
const current = obj[methodKey as keyof T];
// Copy any added properties from current implementation to original
if (current && typeof current === 'function') {
Object.getOwnPropertyNames(current).forEach(prop => {
if (!EXCLUDED_PROPS.includes(prop)) {
try {
(original as any)[prop] = (current as any)[prop];
} catch (e) {
// Ignore property copy errors
}
}
});
}
// Restore original method
try {
obj[methodKey as keyof T] = original;
delete (obj as any)[originalKey];
return true;
} catch (e) {
return false;
}
};
// Case 1: Restore specific method
if (key !== undefined) {
return restoreMethod(String(key));
}
// Case 2: Restore all replaced methods
let anyRestored = false;
const keys = Object.getOwnPropertyNames(obj);
for (const k of keys) {
if (k.startsWith(ORIGINAL_PREFIX)) {
const methodKey = k.slice(ORIGINAL_PREFIX.length);
if (restoreMethod(methodKey)) {
anyRestored = true;
}
}
}
return anyRestored;
compose<T extends Constructor<any>>(target: T, options?: ClsMockOptions<T>): ClsMock<T>;
compose<T extends object>(target: T, options?: {overrides?: DeepPartial<T>} & Partial<Config>): T;
compose<T extends object>(target: T | Constructor<T>, options?: any): any {
const mockObj = compose(target, {...this.currentConfig, ...options});
this.registerMock(mockObj, typeof target === 'function' ? 'class' : 'object', target);
return mockObj;
}
/**
* Gets a list of currently replaced methods on an object
* @template T -The object type to check
* @param obj -The object to check
* @returns {string[]} Array of replaced method names
* Replace a method with mock implementation
* @param obj Target object
* @param key Method key to replace
* @param impl Mock implementation
* @param options Mock configuration
*/
export function getReplacedMethods<T extends object>(obj: T): string[] {
if (!obj) return [];
return Object.getOwnPropertyNames(obj)
.filter(k => k.startsWith(ORIGINAL_PREFIX))
.map(k => k.slice(ORIGINAL_PREFIX.length))
.filter(k => k && typeof obj[k as keyof T] !== 'undefined');
replace<T extends object, K extends keyof T>(obj: T, key: K, impl: Fn, options?: Partial<Config>): void {
replace(obj, key, impl, {...this.currentConfig, ...options});
}
}
/**
* Verifies if a method has been replaced
* @template T -The object type containing the method
* @template K -The key of the method to check
* @param obj -The object containing the method
* @param key -The key of the method to check
* @returns {boolean} Whether the method is currently replaced
*/
export function isReplaced<T extends object, K extends keyof T>(obj: T, key: K): boolean {
if (!obj) return false;
const originalKey = `${ORIGINAL_PREFIX}${String(key)}`;
const original = (obj as any)[originalKey];
const current = obj[key];
return originalKey in obj && current !== original;
}
/**
* Mock function type that combines Mock interface with configuration capability
*/
type MockFn = Mock & ((config?: Partial<Config>) => Mock);
/**
* Verifies if a restored method matches its original implementation
* @template T -The object type containing the method
* @template K -The key of the method to verify
* @param obj -The object containing the method
* @param key -The key of the method to verify
* @returns {boolean} Whether the current implementation matches the original
*/
export function verifyRestored<T extends object, K extends keyof T>(obj: T, key: K): boolean {
if (!obj) return false;
const originalKey = `${ORIGINAL_PREFIX}${String(key)}`;
const original = (obj as any)[originalKey];
// Create a global mock instance for shared configuration
const globalMock = new MockImpl();
// If no original stored, consider it restored
if (!original) return true;
const current = obj[key];
// Direct reference comparison
if (current === original) return true;
// Function implementation comparison
if (typeof current === 'function' && typeof original === 'function') {
try {
// Compare function bodies
const currentStr = current.toString().replace(/\s+/g, '');
const originalStr = original.toString().replace(/\s+/g, '');
return currentStr === originalStr;
} catch {
// If comparison fails, fall back to reference equality
return current === original;
/**
* Creates a new mock instance with optional configuration.
* Provides a unified interface for creating and managing mocks.
*
* @example
* // Create a mock with default configuration
* const defaultMock = mock();
*
* // Create a mock with custom configuration
* const customMock = mock({ strict: true });
*/
export const mock: MockFn = Object.assign(
function mock(config?: Partial<Config>): Mock {
return new MockImpl(config);
},
{
fn: <T extends Fn>(options?: Partial<Config>) => globalMock.fn<T>(options),
obj: <T extends object>(target: T | undefined, options: ObjMockOptions<T> = {}) => globalMock.obj(target, options),
cls: <T extends Constructor<any>>(target: T, options?: ClsMockOptions<T>) => globalMock.cls(target, options),
cast: <T extends object>(partial: DeepPartial<T>, options?: Partial<Config>) => globalMock.cast(partial, options),
replace: <T extends object, K extends keyof T>(obj: T, key: K, impl: Fn, options?: Partial<Config>) =>
globalMock.replace(obj, key, impl, options),
compose: <T extends object>(target: T | Constructor<T>, options?: any) => globalMock.compose(target, options),
configure: function (config?: Partial<Config>): Config | void {
if (!config) {
return globalMock.configure();
}
}
globalMock.configure(config);
},
},
) as MockFn;
return false;
}
/**
* Handles static method inheritance and mocking for class mocks
*/
function handleStaticMethodInheritance(mockClass: any, originalClass: any): void {
let currentProto = Object.getPrototypeOf(originalClass);
while (currentProto && currentProto !== Function.prototype) {
Object.getOwnPropertyNames(currentProto)
.filter(prop => typeof currentProto[prop] === 'function')
.forEach(methodName => {
if (!mockClass[methodName]) {
const spy = createSpy();
spy.mockImplementation(currentProto[methodName].bind(mockClass));
mockClass[methodName] = spy;
}
});
currentProto = Object.getPrototypeOf(currentProto);
}
}
/**
* Creates a mock class that maintains the original class's type information and behavior.
* Supports both full monitoring and selective method mocking.
*
* @template T -The class type to mock
* @param originalClass -The original class to create a mock from
* @param options -Configuration options for class mocking
* @returns {ClsMock<T>} A mock class with the same interface as the original
*
* @remarks
* Class mocking provides comprehensive control over class behavior:
* -Constructor interception
* -Method spying
* -Property tracking
* -Static member handling
* -Inheritance support
*
* Features:
* -Maintains original class structure
* -Supports constructor arguments
* -Handles static members
* -Preserves inheritance chain
* -Allows selective mocking
*
* @example
* ```typescript
* // Monitor all methods
* const MockService = mock.cls(DataService);
*
* // Selective monitoring with custom implementation
* const MockService = mock.cls(DataService, {
* selective: true,
* implementation: {
* getData: async () => 'mocked'
* }
* });
* ```
*
* @throws {Error} When constructor initialization fails
* @see {@link ClsMockOptions} For available configuration options
* @see {@link ClsMock} For the resulting mock class type
*/
export function cls<T extends new (...args: any[]) => any>(
originalClass: T,
options: ClsMockOptions<T> = {},
): ClsMock<T> {
const config = mock.getConfig();
const {selective = false, implementation = {}} = options;
if (config.debug) {
console.debug('Creating class mock for:', originalClass.name);
}
// Helper to create method spy
const createMethodSpy = (method: Function, context: any) => {
const spy = createSpy();
if (config.trackCalls) {
spy.mockImplementation((...args: any[]) => {
if (config.debug) {
console.debug(`Called ${method.name} with:`, args);
}
return method.apply(context, args);
});
} else {
spy.mockImplementation((...args: any[]) => method.apply(context, args));
}
return spy;
};
// Helper to handle property descriptor
const handleDescriptor = (
name: string | symbol,
descriptor: PropertyDescriptor,
context: any,
): PropertyDescriptor => {
// Handle getters/setters
if (descriptor.get || descriptor.set) {
const spies = createPropertySpy();
const implDescriptor =
typeof name === 'string' ? Object.getOwnPropertyDescriptor(implementation, name) : undefined;
return {
configurable: true,
enumerable: true,
get:
descriptor.get &&
(implDescriptor?.get
? spies.get?.mockImplementation(implDescriptor.get)
: !selective
? spies.get?.mockImplementation(descriptor.get.bind(context))
: descriptor.get.bind(context)),
set:
descriptor.set &&
(implDescriptor?.set
? spies.set?.mockImplementation(implDescriptor.set)
: !selective
? spies.set?.mockImplementation(descriptor.set.bind(context))
: descriptor.set.bind(context)),
};
}
// Handle methods
if (typeof descriptor.value === 'function') {
const impl = typeof name === 'string' ? (implementation as any)[name] : undefined;
if (impl) {
const spy = createSpy();
spy.mockImplementation(impl);
return {...descriptor, value: spy};
}
if (!selective) {
return {
...descriptor,
value: createMethodSpy(descriptor.value, context),
};
}
}
return {
...descriptor,
value: descriptor.value,
};
};
// Create mock constructor
function MockClass(this: any, ...args: any[]) {
// Handle constructor call without new
if (!(this instanceof MockClass)) {
return new (MockClass as any)(...args);
}
// Create instance with proper prototype
const instance = Object.create(originalClass.prototype);
try {
// Initialize instance with constructor
const temp = new originalClass(...args);
Object.assign(instance, temp);
} catch {
// Silently continue if constructor fails
}
// Process instance properties and methods
const processMembers = (target: any, source: any) => {
Object.getOwnPropertyNames(source).forEach(name => {
if (name === 'constructor') return;
if (!target.hasOwnProperty(name)) {
const descriptor = Object.getOwnPropertyDescriptor(source, name);
if (descriptor) {
Object.defineProperty(target, name, handleDescriptor(name, descriptor, instance));
}
}
});
};
// Handle instance members
processMembers(instance, originalClass.prototype);
// Handle inherited members
let proto = Object.getPrototypeOf(originalClass.prototype);
while (proto && proto !== Object.prototype) {
processMembers(instance, proto);
proto = Object.getPrototypeOf(proto);
}
Object.setPrototypeOf(instance, MockClass.prototype);
return instance;
}
// Set up prototype chain
MockClass.prototype = Object.create(originalClass.prototype);
MockClass.prototype.constructor = MockClass;
// Handle static members
Object.getOwnPropertyNames(originalClass).forEach(name => {
if (name === 'length' || name === 'prototype' || name === 'name') return;
const descriptor = Object.getOwnPropertyDescriptor(originalClass, name);
if (descriptor) {
Object.defineProperty(MockClass, name, handleDescriptor(name, descriptor, originalClass));
}
});
// Handle static methods inheritance
handleStaticMethodInheritance(MockClass, originalClass);
return MockClass as unknown as ClsMock<T>;
}
/**
* Gets current configuration
*/
export function getConfig(): Readonly<Config> {
return configManager.get();
}
/**
* Updates configuration
*/
export function setConfig(options: Partial<Config>): void {
configManager.set(options);
}
/**
* Resets configuration to defaults
*/
export function resetConfig(): void {
configManager.reset();
}
/**
* Creates a scoped configuration
*/
export function withConfig<T>(options: Partial<Config>, fn: () => T): T {
return configManager.with(options, fn);
}
/**
* Gets configuration statistics
*/
export function getConfigStats(): ConfigStats {
return configManager.getStats();
}
}
export default mock;

@@ -10,17 +10,2 @@ import {Fn, MockFunction} from './types';

* @template T - The type of function being spied on
*
* @example
* ```typescript
* // Create a spy for a simple function
* const spy = new UniversalSpy<() => string>();
* const spyFn = spy.getSpy();
*
* // Configure spy behavior
* spyFn.mockReturnValue('mocked');
*
* // Use the spy
* const result = spyFn();
* expect(result).toBe('mocked');
* expect(spyFn).toHaveBeenCalled();
* ```
*/

@@ -50,3 +35,3 @@ export class UniversalSpy<T extends Fn = Fn> {

} else if (this.implementation) {
result = this.implementation.apply(spy._this || this, args);
result = this.implementation.apply(spy._this, args);
} else {

@@ -56,9 +41,19 @@ result = undefined as unknown as ReturnType<T>;

// Update mock property
spy.mock.calls.push(args);
spy.mock.results.push({type: 'return', value: result});
if (spy._this) {
spy.mock.instances.push(spy._this);
spy.mock.contexts.push(spy._this);
}
finishCall(undefined, result);
return result;
} catch (error) {
// Update mock property for errors
spy.mock.results.push({type: 'throw', value: error});
finishCall(error instanceof Error ? error : new Error(String(error)));
throw error;
}
}) as unknown as MockFunction<T>;
}) as MockFunction<T>;

@@ -81,24 +76,33 @@ // Add Jest mock marker

// Bind the spy to maintain this context
const boundSpy = new Proxy(spy, {
apply: (target, thisArg, argumentsList) => {
spy._this = thisArg;
const result = target.apply(thisArg, argumentsList);
spy._this = null;
return result;
// Add calls property for tracking
spy.calls = {
all: () => this.tracker.getCallsFor('spy').map(call => ({args: call.args as Parameters<T>})),
count: () => this.tracker.getCallsFor('spy').length,
};
// Add mock property
Object.defineProperty(spy, 'mock', {
value: {
calls: [] as Parameters<T>[],
results: [] as Array<{type: 'return' | 'throw'; value: ReturnType<T>}>,
instances: [] as any[],
contexts: [] as any[],
},
writable: true,
configurable: true,
enumerable: true,
});
// Add Jest-like methods
boundSpy.mockReturnValue = (value: ReturnType<T>) => {
spy.mockReturnValue = (value: ReturnType<T>) => {
this.updateState({returnValue: value});
return boundSpy;
return spy;
};
boundSpy.mockResolvedValue = (value: any) => {
spy.mockResolvedValue = (value: any) => {
this.updateState({returnValue: Promise.resolve(value) as unknown as ReturnType<T>});
return boundSpy as any;
return spy as any;
};
boundSpy.mockImplementation = (fn: T) => {
spy.mockImplementation = (fn: T) => {
if (fn !== null && fn !== undefined && typeof fn !== 'function') {

@@ -108,6 +112,6 @@ throw new Error('Mock implementation must be a function');

this.updateState({implementation: fn});
return boundSpy;
return spy;
};
boundSpy.mockReset = () => {
spy.mockReset = () => {
this.tracker.reset();

@@ -117,35 +121,31 @@ this.updateState({});

boundSpy.mockClear = () => {
spy.mockClear = () => {
this.tracker.reset();
};
boundSpy.calls = {
all: () => this.tracker.getCallsFor('spy').map(call => ({args: call.args as Parameters<T>})),
count: () => this.tracker.getCallsFor('spy').length,
};
// Add Jasmine-like methods
boundSpy.and = {
returnValue: (value: ReturnType<T>) => {
boundSpy.mockReturnValue(value);
return boundSpy;
},
callFake: (fn: T) => {
boundSpy.mockImplementation(fn);
return boundSpy;
},
const jasmineApi = {
returnValue: spy.mockReturnValue,
callFake: spy.mockImplementation,
throwError: (error: any) => {
this.updateState({error});
return boundSpy;
return spy;
},
stub: () => {
boundSpy.mockImplementation(undefined as any);
return boundSpy;
spy.mockReset();
return spy;
},
};
Object.defineProperty(spy, 'and', {
value: jasmineApi,
writable: false,
enumerable: true,
configurable: true,
});
// Add common assertions
boundSpy.toHaveBeenCalled = () => this.tracker.getCallsFor('spy').length > 0;
boundSpy.toHaveBeenCalledTimes = (n: number) => this.tracker.getCallsFor('spy').length === n;
boundSpy.toHaveBeenCalledWith = (...args: Parameters<T>) =>
spy.toHaveBeenCalled = () => this.tracker.getCallsFor('spy').length > 0;
spy.toHaveBeenCalledTimes = (n: number) => this.tracker.getCallsFor('spy').length === n;
spy.toHaveBeenCalledWith = (...args: Parameters<T>) =>
this.tracker

@@ -155,2 +155,24 @@ .getCallsFor('spy')

// Bind the spy to maintain this context
const boundSpy = new Proxy(spy, {
apply: (target, thisArg, argumentsList) => {
spy._this = thisArg;
const result = target.apply(thisArg, argumentsList);
spy._this = null;
return result;
},
get: (target, prop: keyof MockFunction<T>) => {
const value = target[prop];
if (typeof value === 'function') {
return function (this: any, ...args: any[]) {
spy._this = this;
const result = value.apply(this, args);
spy._this = null;
return result;
};
}
return value;
},
});
this.spyFunction = boundSpy;

@@ -191,20 +213,4 @@ }

* Creates a new spy function with optional implementation.
* The spy function tracks all calls and provides methods for verifying behavior.
*
* @template T - The type of function to spy on
* @param implementation - Optional implementation for the spy function
* @returns A mock function that tracks calls and provides verification methods
*
* @example
* ```typescript
* // Create a spy with no implementation
* const spy = createSpy<(name: string) => string>();
*
* // Create a spy with implementation
* const spy = createSpy((name: string) => `Hello ${name}`);
*
* // Use the spy
* spy('John');
* expect(spy).toHaveBeenCalledWith('John');
* ```
* @param implementation - Optional implementation for the spy
*/

@@ -217,99 +223,13 @@ export function createSpy<T extends Fn>(implementation?: T): MockFunction<T> {

/**
* Replaces a method on an object with a spy while preserving the original implementation.
* This is useful for monitoring method calls while maintaining the original behavior.
*
* @template T - The type of object containing the method
* @param obj - The object containing the method to spy on
* @param key - The key of the method to spy on
* @param fn - The function to use as implementation
*
* @example
* ```typescript
* class Service {
* getData() { return 'data'; }
* }
*
* const service = new Service();
* replaceFn(service, 'getData', () => 'mock data');
*
* service.getData(); // Returns 'mock data'
* expect(service.getData).toHaveBeenCalled();
* ```
* Creates a spy for a property with getter and/or setter.
* @template T - The type of the property
*/
export function replaceFn<T extends object>(obj: T, key: keyof T, fn: Fn): void {
const spy = createSpy(fn);
spy.mockImplementation(fn);
Object.defineProperty(obj, key, {
value: spy,
writable: true,
configurable: true,
});
}
/**
* Creates a spy for an existing method on an object.
* The spy will track all calls while maintaining the original implementation.
*
* @template T - The type of object containing the method
* @param obj - The object containing the method to spy on
* @param key - The key of the method to spy on
*
* @example
* ```typescript
* class Service {
* getData() { return 'data'; }
* }
*
* const service = new Service();
* spyOn(service, 'getData');
*
* service.getData(); // Returns 'data'
* expect(service.getData).toHaveBeenCalled();
* ```
*/
export function spyOn<T extends object>(obj: T, key: keyof T): void {
const value = obj[key];
if (typeof value === 'function') {
const spy = createSpy(value as Fn);
spy.mockImplementation(value as Fn);
Object.defineProperty(obj, key, {
value: spy,
writable: true,
configurable: true,
});
}
}
/**
* Creates spies for getter/setter properties.
* This is useful for monitoring property access and modifications.
*
* @template T - The type of the property value
* @returns An object containing spy functions for get and set operations
*
* @example
* ```typescript
* class Service {
* private _value: string = '';
* get value() { return this._value; }
* set value(v: string) { this._value = v; }
* }
*
* const service = new Service();
* const spy = createPropertySpy<string>();
*
* Object.defineProperty(service, 'value', {
* get: spy.get,
* set: spy.set
* });
*
* service.value = 'test';
* expect(spy.set).toHaveBeenCalledWith('test');
* ```
*/
export function createPropertySpy<T>(): {get?: MockFunction; set?: MockFunction} {
export function createPropertySpy<T>(): {
get?: MockFunction<() => T>;
set?: MockFunction<(value: T) => void>;
} {
return {
get: createSpy(),
set: createSpy(),
get: createSpy<() => T>(),
set: createSpy<(value: T) => void>(),
};
}

@@ -0,1 +1,3 @@

import type {Config} from '../config';
/**

@@ -24,2 +26,49 @@ * Basic function type

/**
* Object mock builder interface that provides fluent API for building mock objects
*/
export interface ObjectBuilder<T> {
/**
* Apply stubs and options to create a new mock object
* @param stubs Optional partial implementation
* @param options Optional configuration options
*/
with(stubs?: DeepPartial<T>, options?: ObjectOptions<T>): T;
/**
* Add spy functionality to specific methods
* @param methods Methods to spy on
*/
spy(...methods: (keyof T)[]): ObjectBuilder<T>;
/**
* Preserve original implementation of specific properties
* @param properties Properties to preserve
*/
preserve(...properties: (keyof T)[]): ObjectBuilder<T>;
}
/**
* Object mock options for configuring mock behavior
*/
export interface ObjectOptions<T> {
/** Whether to automatically create spies for methods */
autoSpy?: boolean;
/** Whether to handle circular references */
handleCircular?: boolean;
/** Properties to exclude from mocking */
exclude?: Array<keyof T>;
/** Whether to include private properties */
includePrivate?: boolean;
/** Whether to include inherited properties */
includeInherited?: boolean;
/** Track method calls */
trackCalls?: boolean;
}
/**
* Combined options type for object mocking
*/
export type ObjectMockOptions<T> = ObjectOptions<T> & DeepPartial<T> & Partial<Config>;
/**
* Recursive partial type with function handling

@@ -26,0 +75,0 @@ */

@@ -0,1 +1,3 @@

import type {Config} from '../config';
import {cast, cls, compose, fn, obj, replace} from '../mocks';
import type {

@@ -24,32 +26,38 @@ AsyncFn,

/**
* Base mock function interface
* Mock function type with Jest-like chaining methods
*/
interface BaseMockFunction<T extends Fn = Fn> extends Function {
export interface MockFunction<T extends Fn = Fn> extends Function {
(...args: Parameters<T>): ReturnType<T>;
_isMockFunction: boolean;
_this?: any;
mock: {
calls: Parameters<T>[];
results: Array<{type: 'return' | 'throw'; value: any}>;
instances: any[];
contexts: any[];
lastCall?: Parameters<T>;
};
calls: {
all(): Array<{args: Parameters<T>}>;
count(): number;
all: () => Array<{args: Parameters<T>}>;
count: () => number;
};
_isMockFunction?: boolean;
_this?: any;
}
/**
* Mock function type with Jest-like chaining methods
*/
export interface MockFunction<T extends Fn = Fn> extends BaseMockFunction<T> {
// Mock behavior
mockReturnValue(value: ReturnType<T>): this;
mockResolvedValue<U>(value: U): MockFunction<AsyncFn & {(...args: Parameters<T>): Promise<U>}>;
mockImplementation(fn: T): this;
mockReset(): void;
mockClear(): void;
// Jasmine-style methods
mockImplementation: (fn: T) => MockFunction<T>;
mockReturnValue: (value: ReturnType<T>) => MockFunction<T>;
mockResolvedValue: <U>(value: U) => MockFunction<T>;
mockRejectedValue: <U>(value: U) => MockFunction<T>;
mockReset: () => void;
mockClear: () => void;
mockRestore: () => void;
getMockImplementation: () => T | undefined;
getMockName: () => string;
and: MockBehavior<T>;
// Common assertions
toHaveBeenCalled(): boolean;
toHaveBeenCalledTimes(n: number): boolean;
toHaveBeenCalledWith(...args: Parameters<T>): boolean;
toHaveBeenCalled: () => boolean;
toHaveBeenCalledTimes: (n: number) => boolean;
toHaveBeenCalledWith: (...args: Parameters<T>) => boolean;
mockName: (name: string) => MockFunction<T>;
mockReturnThis: () => MockFunction<T>;
mockResolvedValueOnce: <U>(value: U) => MockFunction<T>;
mockRejectedValueOnce: <U>(value: U) => MockFunction<T>;
mockReturnValueOnce: (value: ReturnType<T>) => MockFunction<T>;
mockImplementationOnce: (fn: T) => MockFunction<T>;
}

@@ -60,16 +68,3 @@

*/
export interface JestMock<T extends Fn = Fn> extends BaseMockFunction<T> {
mock: {
calls: Parameters<T>[];
results: Array<{
type: 'return' | 'throw';
value: any;
}>;
};
mockReturnValue(value: ReturnType<T>): this;
mockResolvedValue<U>(value: U): this;
mockImplementation(fn: T): this;
mockReset(): void;
mockClear(): void;
}
export interface JestMock<T extends Fn = Fn> extends MockFunction<T> {}

@@ -79,5 +74,3 @@ /**

*/
export interface JasmineSpy<T extends Fn = Fn> extends BaseMockFunction<T> {
and: MockBehavior<T>;
}
export interface JasmineSpy<T extends Fn = Fn> extends MockFunction<T> {}

@@ -89,3 +82,2 @@ /**

createSpy<T extends Fn = Fn>(): MockFunction<T>;
spyOn<T extends object>(obj: T, key: keyof T): void;
}

@@ -153,4 +145,2 @@

preserveConstructor?: boolean;
selective?: boolean;
preserveThis?: boolean;
autoSpy?: boolean;

@@ -161,2 +151,23 @@ handleCircular?: boolean;

/**
* Base mock options interface
*/
interface BaseMockOptions extends Partial<Config> {
overrides?: DeepPartial<any>;
}
/**
* Options for configuring object mocks
*/
export interface ObjMockOptions<T> extends BaseMockOptions {
overrides?: DeepPartial<T>;
}
/**
* Options for configuring class mocks
*/
export interface ClsMockOptions<T> extends BaseMockOptions {
overrides?: DeepPartial<T>;
}
/**
* Mock class options

@@ -225,27 +236,95 @@ */

*/
export type ClsMock<T extends new (...args: any[]) => any> = StaticMockOf<T> & {
export type ClsMock<T extends new (...args: any[]) => any> = {
new (...args: ConstructorParameters<T>): MockOf<InstanceType<T>>;
prototype: MockOf<InstanceType<T>>;
} & {
[K in keyof T]: T[K] extends (...args: any[]) => any ? MockFunction<T[K]> : T[K];
};
/**
* Options for configuring class mocks
* Partial mock options
*/
export interface ClsMockOptions<T extends new (...args: any[]) => any> extends Pick<MockOptions, 'selective'> {
implementation?: DeepPartial<InstanceType<T>>;
export interface PartialOptions<T> extends Pick<MockOptions, 'autoSpy' | 'handleCircular'> {
overrides?: DeepPartial<T>;
}
/**
* Partial mock options
* Builder interface for partial mocks
*/
export interface PartialOptions<T>
extends Pick<MockOptions, 'selective' | 'preserveThis' | 'autoSpy' | 'handleCircular'> {}
export interface PartialBuilder<T extends object> {
/**
* Apply stubs to create the final mock object
* @param stubs Optional partial implementation
* @param options Optional configuration
*/
with(stubs?: DeepPartial<T>, options?: PartialOptions<T> & Partial<Config>): T;
/**
* Add spy to specific methods
* @param methods Methods to spy on
*/
spy(...methods: (keyof T)[]): PartialBuilder<T>;
/**
* Preserve original implementation of properties
* @param properties Properties to preserve
*/
preserve(...properties: (keyof T)[]): PartialBuilder<T>;
}
/**
* Partial mock builder interface
* Instance mock interface for mocking objects and classes
*/
export interface PartialBuilder<T extends object> {
with(stubs?: DeepPartial<T>, options?: PartialOptions<T>): T;
spy(...methods: (keyof T)[]): this;
preserve(...properties: (keyof T)[]): this;
export interface Mock {
/**
* Gets or sets mock configuration
* @param config - Optional configuration to set
* @returns Current configuration if no arguments, void if setting configuration
*/
configure(): Config;
configure(config: Partial<Config>): void;
/**
* Creates a mock function
*/
fn<T extends Fn>(options?: Partial<Config>): MockFunction<T>;
/**
* Creates a mock object
*/
obj<T extends object>(target: T | undefined, options?: ObjMockOptions<T>): MockObject<T>;
/**
* Creates a mock class with optional implementation
*/
cls<T extends Constructor<any>>(target: T, options?: ClsMockOptions<T>): ClsMock<T>;
/**
* Casts a partial implementation to a complete mock
*/
cast<T extends object>(partial: DeepPartial<T>, options?: Partial<Config>): T;
/**
* Replaces a method with mock implementation
*/
replace<T extends object, K extends keyof T>(obj: T, key: K, impl: Fn, options?: Partial<Config>): void;
/**
* Creates a mock from a class constructor
*/
compose<T extends Constructor<any>>(target: T, options?: ClsMockOptions<T>): ClsMock<T>;
compose<T extends object>(target: T, options?: {overrides?: DeepPartial<T>} & Partial<Config>): T;
}
/**
* Mock object interface that includes utility methods
*/
export type MockObject<T extends object> = T & {
mockClear(): void;
mockReset(): void;
mockRestore(): void;
mockImplementation(implementation: DeepPartial<T>): MockObject<T>;
mockReturnValue(value: any): MockObject<T>;
mockResolvedValue(value: any): MockObject<T>;
mockRejectedValue(value: any): MockObject<T>;
};

@@ -1,2 +0,2 @@

import {MockError, MockErrorCode} from '../../errors';
import {DEFAULT_CONFIG} from '../../config';
import {delay, makeAsync, mockReject, mockResolve, retry, withTimeout} from '../async';

@@ -51,2 +51,47 @@

});
it('should handle errors with custom error factory', async () => {
const customError = new Error('Custom error');
const fn = () => 'result';
const asyncFn = makeAsync(fn, {
errorRate: 1, // Always throw error
errorFactory: () => customError,
});
await expect(asyncFn()).rejects.toBe(customError);
});
it('should respect timeout option with delay', async () => {
const fn = () => 'result';
const asyncFn = makeAsync(fn, {
delay: 2000,
timeout: 100,
});
const promise = asyncFn();
jest.advanceTimersByTime(100);
await expect(promise).rejects.toThrow('Async operation timed out');
});
it('should preserve timing when specified', async () => {
const fn = () => 'result';
const asyncFn = makeAsync(fn, {
delay: 100,
preserveTiming: true,
});
const promise = asyncFn();
jest.advanceTimersByTime(100);
await promise;
});
it('should handle function that throws synchronously', async () => {
const error = new Error('Sync error');
const fn = () => {
throw error;
};
const asyncFn = makeAsync(fn);
await expect(asyncFn()).rejects.toBe(error);
});
});

@@ -80,2 +125,12 @@

});
it('should use default timeout from config', async () => {
const defaultTimeout = DEFAULT_CONFIG.timeout;
const slowPromise = new Promise(resolve => setTimeout(resolve, defaultTimeout + 100));
const timeoutPromise = withTimeout(slowPromise);
jest.advanceTimersByTime(defaultTimeout + 50);
await expect(timeoutPromise).rejects.toThrow('Operation timed out');
});
});

@@ -116,8 +171,7 @@

afterEach(async () => {
await jest.runAllTimersAsync();
await Promise.resolve();
jest.clearAllTimers();
jest.useRealTimers();
});
it('should retry failed operations', async () => {
it('should retry failed operations with exponential backoff', async () => {
let attempts = 0;

@@ -132,8 +186,19 @@ const operation = jest.fn().mockImplementation(async () => {

const resultPromise = retry(operation, 3, 100);
const promise = retry(operation, {
maxAttempts: 3,
baseDelay: 100,
});
// Fast-forward until all timers have been executed
await jest.runAllTimersAsync();
// Initial call
expect(operation).toHaveBeenCalledTimes(1);
const result = await resultPromise;
// First retry (after 100ms)
await jest.advanceTimersByTimeAsync(100);
expect(operation).toHaveBeenCalledTimes(2);
// Second retry (after 200ms more)
await jest.advanceTimersByTimeAsync(200);
expect(operation).toHaveBeenCalledTimes(3);
const result = await promise;
expect(result).toBe('success');

@@ -143,34 +208,42 @@ expect(attempts).toBe(3);

it('should use exponential backoff', async () => {
const operation = jest.fn().mockRejectedValue(new Error('Failure'));
const resultPromise = retry(operation, 3, 100);
it('should fail after max attempts', async () => {
jest.useRealTimers();
const operation = jest.fn().mockRejectedValue(new Error('Persistent failure'));
// Initial call executes immediately
expect(operation).toHaveBeenCalledTimes(1);
const promise = retry(operation, {
maxAttempts: 2,
baseDelay: 10, // shorter delay
});
// First retry (after 100ms)
await jest.advanceTimersByTimeAsync(99);
expect(operation).toHaveBeenCalledTimes(1);
await jest.advanceTimersByTimeAsync(1);
await expect(promise).rejects.toThrow(/Persistent failure \(after 2 attempts\)/);
expect(operation).toHaveBeenCalledTimes(2);
});
// Second retry (after 200ms)
await jest.advanceTimersByTimeAsync(199);
// TODO: Fix this test. It's not stable enough to check for timeout
it.skip('should respect timeout option', async () => {
jest.useRealTimers();
const operation = jest.fn().mockRejectedValue(new Error('Failure'));
const promise = retry(operation, {
maxAttempts: 3,
baseDelay: 100,
timeout: 200,
});
await expect(promise).rejects.toThrow('Operation timed out');
expect(operation).toHaveBeenCalledTimes(2);
});
// For the final retry, we must use synchronous timer advancement
// This is due to how Jest handles Promise microtasks:
// 1. advanceTimersByTimeAsync immediately processes all microtasks after timer advancement
// 2. This causes the final MockError to be treated as an unhandled rejection
// 3. Using synchronous advanceTimersByTime + Promise.resolve() gives us control over
// when the Promise microtasks are processed
// 4. This ensures the error is properly caught by our expect assertion
// rather than being caught by Jest as an unhandled rejection
jest.advanceTimersByTime(1);
await Promise.resolve();
expect(operation).toHaveBeenCalledTimes(3);
it('should succeed on first attempt', async () => {
const operation = jest.fn().mockResolvedValue('immediate success');
await expect(resultPromise).rejects.toThrow('Failure (after 3 attempts)');
const promise = retry(operation, {
maxAttempts: 3,
baseDelay: 100,
});
const result = await promise;
expect(result).toBe('immediate success');
expect(operation).toHaveBeenCalledTimes(1);
});
});
});

@@ -51,16 +51,2 @@ import {MockError} from '../../errors';

it('should handle undefined methods based on options', () => {
const mock = createChainable(target, {allowUndefined: false});
expect(() => {
(mock as any).undefinedMethod();
}).toThrow(MockError);
});
it('should allow undefined methods when configured', () => {
const mock = createChainable(target, {allowUndefined: true});
expect(() => {
(mock as any).undefinedMethod();
}).not.toThrow();
});
it('should cache method wrappers', () => {

@@ -67,0 +53,0 @@ const mock = createChainable(target);

@@ -5,5 +5,7 @@ /**

import {mockConfig} from '../config';
import {MockError, MockErrorCode} from '../errors';
// Default timeout value in milliseconds
const DEFAULT_TIMEOUT = 5000;
/**

@@ -34,3 +36,3 @@ * Async method options

delay = 0,
timeout = mockConfig.get().timeout,
timeout = DEFAULT_TIMEOUT,
errorRate = 0,

@@ -43,4 +45,5 @@ errorFactory = () => new Error('Async operation failed'),

// Create timeout promise
let timeoutId: NodeJS.Timeout | undefined;
const timeoutPromise = new Promise<never>((_, reject) => {
setTimeout(() => {
timeoutId = setTimeout(() => {
reject(new Error('Async operation timed out'));

@@ -51,2 +54,3 @@ }, timeout);

// Create main operation promise
let delayId: NodeJS.Timeout | undefined;
const operationPromise = new Promise<ReturnType<T>>((resolve, reject) => {

@@ -69,3 +73,3 @@ const execute = () => {

if (delay > 0) {
setTimeout(execute, delay);
delayId = setTimeout(execute, delay);
} else {

@@ -76,4 +80,10 @@ execute();

// Race between timeout and operation
return Promise.race([operationPromise, timeoutPromise]);
try {
// Race between timeout and operation
return await Promise.race([operationPromise, timeoutPromise]);
} finally {
// Clean up timers
if (timeoutId) clearTimeout(timeoutId);
if (delayId) clearTimeout(delayId);
}
};

@@ -92,5 +102,6 @@ }

*/
export function withTimeout<T>(promise: Promise<T>, ms: number = mockConfig.get().timeout): Promise<T> {
export function withTimeout<T>(promise: Promise<T>, ms: number = DEFAULT_TIMEOUT): Promise<T> {
let timeoutId: NodeJS.Timeout | undefined;
const timeoutPromise = new Promise<never>((_, reject) => {
setTimeout(() => {
timeoutId = setTimeout(() => {
reject(new Error('Operation timed out'));

@@ -100,3 +111,10 @@ }, ms);

return Promise.race([promise, timeoutPromise]);
try {
return Promise.race([promise, timeoutPromise]).finally(() => {
if (timeoutId) clearTimeout(timeoutId);
});
} catch (error) {
if (timeoutId) clearTimeout(timeoutId);
throw error;
}
}

@@ -120,27 +138,77 @@

/**
* Retries an async operation with exponential backoff
* Retry options for configuring retry behavior
*/
export async function retry<T>(
operation: () => Promise<T>,
maxAttempts: number = 3,
baseDelay: number = 1000,
): Promise<T> {
export interface RetryOptions {
/** Maximum number of attempts */
maxAttempts?: number;
/** Base delay between retries in ms */
baseDelay?: number;
/** Maximum total time for all retries in ms */
timeout?: number;
/** Whether to include attempt count in error messages */
includeAttemptCount?: boolean;
}
/**
* Retries an async operation with exponential backoff and timeout
*/
export async function retry<T>(operation: () => Promise<T>, options: RetryOptions = {}): Promise<T> {
const {maxAttempts = 3, baseDelay = 1000, timeout = DEFAULT_TIMEOUT, includeAttemptCount = true} = options;
let lastError: Error | undefined;
let attempts = 0;
const startTime = Date.now();
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
try {
return await operation();
} catch (error) {
lastError = error as Error;
if (attempt < maxAttempts) {
const delay = baseDelay * Math.pow(2, attempt - 1);
await new Promise(resolve => setTimeout(resolve, delay));
}
}
}
// Create cleanup function for timeouts
const timeouts: NodeJS.Timeout[] = [];
const cleanup = () => {
timeouts.forEach(clearTimeout);
timeouts.length = 0;
};
if (lastError) {
lastError.message = `${lastError.message} (after ${maxAttempts} attempts)`;
// Ensure cleanup on process exit
const cleanupHandler = () => cleanup();
process.on('beforeExit', cleanupHandler);
try {
return await withTimeout(
(async () => {
while (attempts < maxAttempts) {
attempts++;
try {
return await operation();
} catch (error) {
lastError = error instanceof Error ? error : new Error(String(error));
if (attempts < maxAttempts) {
const elapsedTime = Date.now() - startTime;
const remainingTime = timeout - elapsedTime;
// Check if we have enough time for next retry
if (remainingTime <= 0) {
throw new Error('Operation timed out');
}
const delayTime = Math.min(baseDelay * Math.pow(2, attempts - 1), remainingTime);
await new Promise<void>(resolve => {
const timeoutId = setTimeout(resolve, delayTime);
timeouts.push(timeoutId);
});
}
}
}
const finalError = lastError || new Error('Operation failed');
if (includeAttemptCount) {
finalError.message = `${finalError.message} (after ${maxAttempts} attempts)`;
}
throw finalError;
})(),
timeout,
);
} finally {
cleanup();
process.off('beforeExit', cleanupHandler);
}
throw lastError || new Error('Operation failed');
}

@@ -14,4 +14,2 @@ /**

trackCalls?: boolean;
/** Whether to allow undefined methods */
allowUndefined?: boolean;
/** Custom method implementation */

@@ -34,3 +32,3 @@ implementation?: Record<string, Function>;

export function createChainable<T extends object>(target: T, options: ChainableOptions = {}): T {
const {trackCalls = true, allowUndefined = true, implementation = {}} = options;
const {trackCalls = true, implementation = {}} = options;

@@ -100,7 +98,2 @@ const tracker = trackCalls ? new MethodTracker() : undefined;

// Handle undefined methods
if (!allowUndefined && value === undefined) {
throw MockError.invalidImplementation(`Method ${String(prop)} is not implemented`);
}
// Return chainable function for undefined methods

@@ -107,0 +100,0 @@ const chainableMethod = (...args: any[]) => {

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc