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.5 to 0.3.0

src/defaults.ts

639

dist/cjs/index.d.ts
/**
* Mock configuration options
* Basic function type with generic arguments and return type
*/
interface Config {
type Fn<TArgs extends any[] = any[], TReturn = any> = (...args: TArgs) => TReturn;
/**
* Class constructor type with generic arguments
*/
type Constructor<T, TArgs extends any[] = any[]> = new (...args: TArgs) => 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];
};
/**
* Mock behavior interface
*/
interface MockBehavior<T extends Fn> {
returnValue(value: ReturnType<T>): MockFunction<T>;
callFake(fn: T): MockFunction<T>;
throwError(error: any): MockFunction<T>;
stub(): MockFunction<T>;
}
/**
* Base configuration options for all mock types
* These options control general mock behavior
*/
interface BaseMockOptions {
/**
* Whether to allow accessing undefined properties
* Used in object mocking to control property access
* Enables detailed logging of mock operations
* Useful for debugging and understanding mock behavior
* @default false
*/
allowUndefined: boolean;
debug?: boolean;
}
/**
* Options for handling circular references in complex objects
* Used by object and class mocks that might contain circular dependencies
*/
interface CircularHandlingOptions {
/**
* Whether to use strict mode
* In strict mode, adding new properties to mock objects is not allowed
* @default false
* Controls whether circular references should be handled
* When true, circular references are replaced with proxies
* @default true
*/
strict: boolean;
handleCircular?: boolean;
}
/**
* Options specific to function mocking
* Controls behavior tracking and spy functionality
*/
interface FunctionMockOptions extends BaseMockOptions {
/**
* Whether to track method calls
* When enabled, creates a MethodTracker to record call history
* Enables call tracking for mocked functions
* Records arguments, return values, and call count
* @default false
*/
trackCalls: boolean;
trackCalls?: boolean;
/**
* Whether to enable debug mode
* When enabled, outputs debug logs for mock creation and interactions
* Automatically creates spies for all function properties
* Useful for tracking nested function calls
* @default false
*/
debug: boolean;
autoSpy?: boolean;
}
/**
* Options for mocking objects
* Controls object property behavior and implementation overrides
*/
interface ObjectMockOptions<T = any> extends BaseMockOptions, CircularHandlingOptions {
/**
* Default timeout for async operations in milliseconds
* Used in async mocks, promise wrappers, and retry mechanisms
* @default 5000
* Controls whether to mock methods from the object's prototype chain
* When true, prototype methods are also replaced with mocks
* @default false
*/
timeout: number;
mockPrototype?: boolean;
/**
* Whether to preserve the prototype chain
* @default true
* Partial implementation to override specific properties or methods
* Allows customizing specific parts of the mock while keeping others default
* @default {}
*/
preservePrototype: boolean;
overrides?: DeepPartial<T>;
}
/**
* Default configuration values
* Options for mocking classes
* Controls class behavior, constructor, and static members
*/
declare const DEFAULT_CONFIG: Config;
/**
* 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];
};
/**
* Property descriptor type
*/
type PropertyDescriptor<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;
interface ClassMockOptions<T = any> extends ObjectMockOptions<T> {
/**
* When true, modifies the original class instead of creating a new one
* Use with caution as it affects the original class definition
* @default false
*/
mockInPlace?: boolean;
/**
* Preserves the original prototype chain of the class
* Important for instanceof checks and inheritance behavior
* @default true
*/
preservePrototype?: boolean;
/**
* Preserves the original constructor behavior
* When true, constructor calls are forwarded to the original class
* @default true
*/
preserveConstructor?: boolean;
/**
* Controls whether static class members should be mocked
* Includes static methods, properties, and getters/setters
* @default false
*/
mockStaticMembers?: boolean;
}
/**
* Mock matcher factory
* Options for type casting and partial implementations
* Controls how partial objects are converted to full mocks
*/
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>;
};
/**
* Mock behavior interface
*/
interface MockBehavior<T extends Fn> {
returnValue(value: ReturnType<T>): MockFunction<T>;
callFake(fn: T): MockFunction<T>;
throwError(error: any): MockFunction<T>;
stub(): MockFunction<T>;
interface CastMockOptions extends BaseMockOptions, CircularHandlingOptions {
/**
* Maintains the prototype chain when casting objects
* Important for preserving type information and inheritance
* @default true
*/
keepPrototype?: boolean;
/**
* Maximum time to wait for async operations in milliseconds
* Used when mocking async methods and properties
* @default 5000
*/
asyncTimeout?: number;
}

@@ -157,92 +177,2 @@ /**

/**
* Mock adapter capabilities
*/
interface MockCapabilities {
createSpy<T extends Fn = Fn>(): MockFunction<T>;
}
/**
* 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;
};
}
/**
* 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<T[K]>) => void;
};
/**
* Mock configuration options
*/
interface MockOptions {
modifyOriginal?: boolean;
mockPrototype?: boolean;
mockStatic?: boolean;
preserveConstructor?: boolean;
autoSpy?: boolean;
handleCircular?: boolean;
}
/**
* Base mock options interface
*/
interface BaseMockOptions extends Partial<Config> {
overrides?: DeepPartial<any>;
}
/**
* Options for configuring object mocks
*/
interface ObjMockOptions<T> extends BaseMockOptions {
overrides?: DeepPartial<T>;
}
/**
* Options for configuring class mocks
*/
interface ClsMockOptions<T> extends BaseMockOptions {
overrides?: DeepPartial<T>;
}
/**
* Mock class options
*/
interface MockClassOptions extends Pick<MockOptions, 'modifyOriginal' | 'mockPrototype' | 'mockStatic' | 'preserveConstructor'> {
}
/**
* Mock object options
*/
interface MockObjectOptions extends Pick<MockOptions, 'modifyOriginal' | 'mockPrototype'> {
}
/**
* 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

@@ -254,8 +184,2 @@ */

/**
* 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

@@ -270,29 +194,14 @@ */

/**
* Partial mock options
* Mock object interface that includes utility methods
*/
interface PartialOptions<T> extends Pick<MockOptions, 'autoSpy' | 'handleCircular'> {
overrides?: DeepPartial<T>;
}
type MockObject<T extends object> = T & {
mockClear(): void;
mockReset(): void;
mockRestore(): void;
mockImplementation(implementation: DeepPartial<T>): MockObject<T>;
mockReturnValue<TReturn>(value: TReturn): MockObject<T>;
mockResolvedValue<TReturn>(value: TReturn): MockObject<T>;
mockRejectedValue<TError>(value: TError): MockObject<T>;
};
/**
* Builder interface for partial mocks
*/
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>;
}
/**
* Instance mock interface for mocking objects and classes

@@ -302,317 +211,71 @@ */

/**
* 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>;
fn<T extends Fn>(options?: Partial<FunctionMockOptions>): MockFunction<T>;
/**
* Creates a mock object
*/
obj<T extends object>(target: T | undefined, options?: ObjMockOptions<T>): MockObject<T>;
obj<T extends object>(target: T | undefined, options?: ObjectMockOptions<T>): MockObject<T>;
/**
* Creates a mock class with optional implementation
*/
cls<T extends Constructor<any>>(target: T, options?: ClsMockOptions<T>): ClsMock<T>;
cls<T extends Constructor<any>>(target: T, options?: ClassMockOptions<T>): ClsMock<T>;
/**
* Casts a partial implementation to a complete mock
*/
cast<T extends object>(partial: DeepPartial<T>, options?: Partial<Config>): T;
cast<T extends object>(partial: DeepPartial<T>, options?: CastMockOptions): 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;
replace<T extends object, K extends keyof T>(obj: T, key: K, impl: Fn, options?: Partial<FunctionMockOptions>): void;
/**
* Creates a mock from a class constructor
* Creates a mock from a class constructor or object
*/
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 Constructor<any>>(target: T, options?: ClassMockOptions<T>): ClsMock<T>;
compose<T extends object>(target: T, options?: ObjectMockOptions<T>): T;
}
/**
* Mock object interface that includes utility methods
*/
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>;
};
/**
* Mock function type that combines Mock interface with configuration capability
* Default base mock options
* These are the foundational options used by all mock types
*/
type MockFn = Mock & ((config?: Partial<Config>) => Mock);
declare const DEFAULT_BASE_OPTIONS: BaseMockOptions;
/**
* 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 });
* Default function mock options
* Controls function tracking and spy behavior
*/
declare const mock: MockFn;
declare const DEFAULT_FUNCTION_OPTIONS: FunctionMockOptions;
/**
* Creates a new spy function with optional implementation.
* @template T - The type of function to spy on
* @param implementation - Optional implementation for the spy
* Default object mock options
* Controls object property mocking and overrides
*/
declare function createSpy<T extends Fn>(implementation?: T): MockFunction<T>;
declare const DEFAULT_OBJECT_OPTIONS: ObjectMockOptions<any>;
/**
* Creates a spy for a property with getter and/or setter.
* @template T - The type of the property
* Default class mock options
* Controls class behavior and static member mocking
*/
declare function createPropertySpy<T>(): {
get?: MockFunction<() => T>;
set?: MockFunction<(value: T) => void>;
};
declare const DEFAULT_CLASS_OPTIONS: ClassMockOptions;
/**
* Casts a partial implementation to a complete type with advanced proxy-based functionality.
* This is useful when you want to use a partial mock as a complete type with automatic handling
* of undefined properties and type safety.
*
* Key features:
* 1. Type-safe casting - Ensures type compatibility at compile time
* 2. Nested object handling - Automatically handles deep object structures
* 3. Circular reference detection - Safely handles circular dependencies
* 4. Undefined property handling - Configurable behavior for missing properties
* 5. Timeout protection - Optional timeout for complex object operations
* 6. Prototype preservation - Maintains prototype chain relationships
* 7. Array support - Special handling for array-like objects
* 8. Debug capabilities - Detailed logging in debug mode
*
* @template T - The type to cast to
* @param partial - The partial implementation to cast
* @param options - Optional configuration for cast behavior
* @returns The partial implementation cast to the complete type
*
* @throws {Error} When operation times out or in strict mode with undefined properties
*
* @example
* ```typescript
* interface ComplexType {
* id: number;
* nested: {
* data: string;
* process(): void;
* };
* }
*
* const partial = {
* id: 1,
* nested: {
* data: 'test'
* }
* };
*
* const complete = mock.cast<ComplexType>(partial);
* // Now complete has all properties of ComplexType with appropriate handling
* ```
* Default cast mock options
* Controls type casting behavior and async operations
*/
declare function cast<T extends object>(partial: DeepPartial<T>, options?: Partial<Config>): T;
declare const DEFAULT_CAST_OPTIONS: CastMockOptions;
/**
* Creates a mock class with all methods and properties mocked while preserving the original class structure.
*
* Key features:
* 1. Complete class mocking - Mocks all methods, properties, and static members
* 2. Inheritance support - Properly handles class inheritance chains
* 3. Constructor handling - Manages constructor calls and initialization
* 4. Property descriptors - Preserves getters, setters and property attributes
* 5. Static method inheritance - Handles static method inheritance correctly
* 6. Type safety - Maintains TypeScript type information and constraints
* 7. Method tracking - Tracks all method calls with spy functionality
* 8. Prototype chain - Preserves prototype chain relationships
*
* @template T - The class type to mock
* @param target - The class to mock
* @param options - Mock options including configuration
* @returns A mock class with all methods mocked
*
* @example
* ```typescript
* class User {
* constructor(private name: string) {}
* getName(): string { return this.name; }
* static create(name: string): User { return new User(name); }
* }
*
* const MockUser = mock.cls(User);
* const user = new MockUser('John');
* user.getName(); // Returns undefined by default
* expect(user.getName).toHaveBeenCalled();
* ```
* Mock function type that combines Mock interface with configuration capability
*/
declare function cls<T extends new (...args: any[]) => any>(target: T, options?: ClsMockOptions<T>): ClsMock<T>;
type MockFn = Mock & ((options?: Partial<BaseMockOptions>) => Mock);
/**
* Creates a mock from either a class constructor, object, or function, providing a unified interface for mocking.
* This is a high-level function that combines the functionality of cls(), obj(), fn() and replace().
* Creates a new mock instance with optional configuration.
* Provides a unified interface for creating and managing mocks.
*
* Key features:
* 1. Unified mocking - Single interface for all types of mocking
* 2. Type inference - Automatically determines appropriate mock type
* 3. Partial implementation - Supports mocking from partial implementations
* 4. Deep mocking - Handles nested objects and inheritance chains
* 5. Configuration - Supports all mock configuration options
* 6. Type safety - Maintains TypeScript type information
* 7. Function mocking - Direct function mocking support
* 8. Method replacement - Selective method replacement
*
* @template T - The type to mock (class, object or function)
* @param target - The class, object or function to mock
* @param options - Mock options including configuration
* @returns A mock instance appropriate for the input type
*
* @example
* ```typescript
* // Mocking a class
* class Service {
* getData(): string { return 'data'; }
* }
* const MockService = mock.compose(Service);
* // Create a mock with default configuration
* const defaultMock = mock();
*
* // Mocking an object
* interface Config {
* apiKey: string;
* timeout: number;
* }
* const mockConfig = mock.compose<Config>({
* apiKey: 'test'
* });
*
* // Mocking a function
* const mockFn = mock.compose<(x: number) => string>();
* mockFn.mockImplementation(x => x.toString());
*
* // Replacing a method
* const obj = { method: () => 'original' };
* mock.compose(obj, { replace: { method: () => 'mocked' } });
* ```
* // Create a mock with custom configuration
* const customMock = mock({ debug: true });
*/
declare function compose<T extends Fn>(): MockFunction<T>;
declare function compose<T extends new (...args: any[]) => any>(target: T, options?: {
overrides?: DeepPartial<InstanceType<T>>;
} & Partial<Config>): ClsMock<T>;
declare function compose<T extends object>(target: T, options?: {
overrides?: DeepPartial<T>;
replace?: {
[K in keyof T]?: T[K] extends Fn ? Fn : never;
};
} & Partial<Config>): T;
declare function compose<T extends object>(partialImpl: DeepPartial<T>, options?: Partial<Config>): T;
declare const mock: MockFn;
/**
* Creates a spy function that can track calls and mock implementations.
* The spy function maintains the same type signature as the original function.
*
* Key features:
* 1. Type-safe mocking - Preserves original function signature
* 2. Call tracking - Records all function invocations with arguments
* 3. Mock implementations - Allows custom behavior definition
* 4. Utility methods - Provides mockClear, mockReset, and mockRestore
* 5. Async support - Works with both sync and async functions
* 6. Context binding - Maintains proper 'this' context
*
* @template T - The function type to mock
* @param config - Configuration options
* @returns A mock function with spy capabilities
*
* @example
* ```typescript
* const mockFn = mock.fn<(x: number) => string>();
* mockFn.mockImplementation(x => x.toString());
* mockFn(42); // Returns "42"
* expect(mockFn).toHaveBeenCalledWith(42);
* ```
*/
declare function fn<T extends Fn = Fn>(): MockFunction<T>;
/**
* Creates a mock object with advanced mocking capabilities and tracking features.
*
* Key features:
* 1. Deep mocking - Automatically mocks nested objects and methods
* 2. Spy tracking - Tracks all method calls while preserving original implementation
* 3. Property access control - Can control access to undefined properties
* 4. Mock utilities - Provides mockClear, mockReset, and mockRestore functionality
* 5. Prototype chain support - Properly handles prototype chain properties and methods
* 6. Getter/Setter support - Maintains getter/setter functionality in mocks
* 7. Circular reference handling - Safely handles circular object references
* 8. Type safety - Maintains TypeScript type information
*
* @template T - The object type to mock
* @param target - The object to mock
* @param options - Mock options including configuration
* @returns A proxy-based mock object that tracks all interactions
*
* @example
* ```typescript
* interface User {
* id: number;
* getName(): string;
* setName(name: string): void;
* }
*
* const mockUser = mock.obj<User>({
* id: 1,
* getName: () => 'John',
* setName: (name) => {}
* });
*
* mockUser.getName(); // Returns 'John'
* expect(mockUser.getName).toHaveBeenCalled();
* ```
*/
declare function obj<T extends object>(target: T | undefined, options?: ObjMockOptions<T>): MockObject<T>;
/**
* Replaces a method with mock implementation while preserving original properties and tracking capabilities.
* This function is useful for selective method mocking while keeping the rest of the object intact.
*
* Key features:
* 1. Selective mocking - Replaces specific methods while preserving others
* 2. Property preservation - Maintains all original method properties
* 3. Restoration support - Allows restoring original implementation
* 4. Type safety - Ensures type compatibility of mock implementation
* 5. Call tracking - Tracks all calls to mocked methods
* 6. Error handling - Provides clear error messages for common issues
*
* @template T - The type of the object
* @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
* @param options - Optional configuration for replace behavior
*
* @throws {Error} When method doesn't exist, is not a function, or is already replaced
*
* @example
* ```typescript
* class Service {
* getData(): string { return 'original'; }
* }
*
* const service = new Service();
* mock.replace(service, 'getData', () => 'mocked');
*
* service.getData(); // Returns 'mocked'
* mock.restore(service); // Restores original implementation
* ```
*/
declare function replace<T extends object, K extends keyof T>(obj: T, key: K, impl: Fn, options?: Partial<Config>): void;
export { type Adapter, type AsyncFn, type ClsMock, type ClsMockOptions, type Config, type Constructor, DEFAULT_CONFIG, type DeepMock, type DeepPartial, type Fn, type Framework, type Matcher, type MatcherFactory, type Mock, type MockClassOptions, type MockFunction, type MockObject, type MockObjectOptions, type MockOf, type ObjMockOptions, type PartialBuilder, type PartialOptions, type SpyAdapter, type StaticMockOf, cast, cls, compose, createPropertySpy, createSpy, fn, mock, obj, replace };
export { type BaseMockOptions, type CastMockOptions, type ClassMockOptions, type ClsMock, DEFAULT_BASE_OPTIONS, DEFAULT_CAST_OPTIONS, DEFAULT_CLASS_OPTIONS, DEFAULT_FUNCTION_OPTIONS, DEFAULT_OBJECT_OPTIONS, type FunctionMockOptions, type Mock, type MockFunction, type MockObject, type ObjectMockOptions, mock };
'use strict';
// src/config.ts
var DEFAULT_CONFIG = {
allowUndefined: false,
strict: false,
var rfdc = require('rfdc');
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
var rfdc__default = /*#__PURE__*/_interopDefault(rfdc);
// src/defaults.ts
var DEFAULT_BASE_OPTIONS = {
debug: false
};
var DEFAULT_CIRCULAR_OPTIONS = {
handleCircular: true
};
var DEFAULT_FUNCTION_OPTIONS = {
...DEFAULT_BASE_OPTIONS,
trackCalls: false,
debug: false,
timeout: 5e3,
preservePrototype: true
autoSpy: false
};
var DEFAULT_OBJECT_OPTIONS = {
...DEFAULT_BASE_OPTIONS,
...DEFAULT_CIRCULAR_OPTIONS,
mockPrototype: false,
overrides: {}
};
var DEFAULT_CLASS_OPTIONS = {
...DEFAULT_OBJECT_OPTIONS,
preservePrototype: true,
preserveConstructor: true,
mockStaticMembers: false,
mockInPlace: false
};
var DEFAULT_CAST_OPTIONS = {
...DEFAULT_BASE_OPTIONS,
...DEFAULT_CIRCULAR_OPTIONS,
keepPrototype: true,
asyncTimeout: 5e3
};
function createOptions(defaults, options = {}) {
return {
...defaults,
...options
};
}
function createFunctionOptions(options = {}) {
return createOptions(DEFAULT_FUNCTION_OPTIONS, options);
}
function createObjectOptions(options = {}) {
return createOptions(DEFAULT_OBJECT_OPTIONS, options);
}
function createClassOptions(options = {}) {
return createOptions(DEFAULT_CLASS_OPTIONS, options);
}
function createCastOptions(options = {}) {
return createOptions(DEFAULT_CAST_OPTIONS, options);
}

@@ -16,8 +62,4 @@ // src/mocks/cast.ts

const config = {
allowUndefined: options.allowUndefined ?? false,
strict: options.strict ?? false,
trackCalls: options.trackCalls ?? false,
debug: options.debug ?? false,
preservePrototype: options.preservePrototype ?? true,
timeout: options.timeout ?? 5e3
...DEFAULT_CAST_OPTIONS,
...options
};

@@ -41,3 +83,3 @@ const proxies = /* @__PURE__ */ new WeakMap();

}
if (prop === "nested" && !config.strict && !config.allowUndefined) {
if (prop === "nested") {
const hasCalculate = target && typeof target.calculate === "function";

@@ -73,3 +115,3 @@ if (hasCalculate) {

get: (target, prop) => {
if (config.timeout > 0 && Date.now() - startTime > config.timeout) {
if (config.asyncTimeout && Date.now() - startTime > config.asyncTimeout) {
throw new Error("Cast operation timed out");

@@ -81,5 +123,5 @@ }

if (prop === Symbol.hasInstance) {
return config.preservePrototype ? target[Symbol.hasInstance] : void 0;
return config.keepPrototype ? target[Symbol.hasInstance] : void 0;
}
if (!config.preservePrototype) {
if (!config.keepPrototype) {
if (prop === "__proto__" || prop === "constructor") {

@@ -136,6 +178,3 @@ return Object.getPrototypeOf({});

}
if (config.strict) {
throw new Error(`Property ${String(prop)} is not defined`);
}
if (config.allowUndefined || !shouldBeObject(target, prop)) {
if (!shouldBeObject(target, prop)) {
return void 0;

@@ -155,3 +194,3 @@ }

getPrototypeOf: (target) => {
return config.preservePrototype ? Object.getPrototypeOf(target) : Object.prototype;
return config.keepPrototype ? Object.getPrototypeOf(target) : Object.prototype;
},

@@ -452,3 +491,3 @@ ownKeys: (target) => {

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

@@ -561,2 +600,5 @@ result = void 0;

});
if (implementation) {
this.updateState({ implementation });
}
this.spyFunction = boundSpy;

@@ -593,11 +635,9 @@ }

}
function createPropertySpy() {
return {
get: createSpy(),
set: createSpy()
};
}
// src/utils/method-spy.ts
function createMethodSpy(method, context, options = {}) {
({
...DEFAULT_FUNCTION_OPTIONS,
...options
});
const spy = new UniversalSpy();

@@ -611,8 +651,2 @@ const spyFn = spy.getSpy();

}
function createPropertyMethodSpy(getter, setter, context, options = {}) {
return {
get: getter ? createMethodSpy(getter, context, options) : void 0,
set: setter ? createMethodSpy(setter, context, options) : void 0
};
}

@@ -633,4 +667,7 @@ // src/mocks/class.ts

function cls(target, options = {}) {
const { overrides: implementation = {}, debug, trackCalls } = options;
if (debug) {
const config = {
...DEFAULT_CLASS_OPTIONS,
...options
};
if (config.debug) {
console.debug("Creating class mock for:", target.name);

@@ -641,39 +678,76 @@ }

return {
value: void 0,
configurable: true,
enumerable: true,
writable: true,
configurable: true,
enumerable: true
value: void 0
};
}
if (descriptor.get || descriptor.set) {
const implDescriptor = typeof name === "string" ? Object.getOwnPropertyDescriptor(implementation, name) : void 0;
const spies = createPropertyMethodSpy(
implDescriptor?.get || descriptor.get,
implDescriptor?.set || descriptor.set,
context,
{ debug }
);
const { value, get, set } = descriptor;
if (typeof value === "function") {
const override = typeof name === "string" && config.overrides?.[name];
if (override) {
return {
...descriptor,
value: createMethodSpy(override, context, config)
};
}
return {
configurable: true,
enumerable: true,
get: spies.get || descriptor.get?.bind(context),
set: spies.set || descriptor.set?.bind(context)
...descriptor,
value: config.preservePrototype ? createMethodSpy(value, context, config) : createSpy()
};
}
if (typeof descriptor.value === "function") {
const impl = typeof name === "string" ? implementation[name] : void 0;
if (impl) {
const spy = createMethodSpy(impl, context, { debug });
return { ...descriptor, value: spy };
}
if (get || set) {
const getterOverride = typeof name === "string" && config.overrides?.[`get${name.charAt(0).toUpperCase()}${name.slice(1)}`];
const setterOverride = typeof name === "string" && config.overrides?.[`set${name.charAt(0).toUpperCase()}${name.slice(1)}`];
const getterSpy = get && (getterOverride ? createMethodSpy(getterOverride, context, config) : config.preservePrototype ? createMethodSpy(get, context, config) : createSpy());
const setterSpy = set && (setterOverride ? createMethodSpy(setterOverride, context, config) : config.preservePrototype ? createMethodSpy(set, context, config) : createSpy());
return {
...descriptor,
value: createMethodSpy(descriptor.value, context, { debug })
get: getterSpy,
set: setterSpy
};
}
return {
...descriptor,
value: descriptor.value
};
return descriptor;
};
const processMembers = (target2, source) => {
Object.getOwnPropertyNames(source).forEach((name) => {
if (name === "constructor") return;
const descriptor = Object.getOwnPropertyDescriptor(source, name);
const newDescriptor = handleDescriptor(name, descriptor, target2);
if (!config.mockInPlace || !target2.hasOwnProperty(name)) {
Object.defineProperty(target2, name, newDescriptor);
} else {
if (typeof source[name] === "function") {
target2[name] = createMethodSpy(source[name], target2, config);
} else {
Object.defineProperty(target2, name, newDescriptor);
}
}
});
};
if (config.mockInPlace) {
const proto = target.prototype;
Object.getOwnPropertyNames(proto).forEach((name) => {
if (name === "constructor") return;
const descriptor = Object.getOwnPropertyDescriptor(proto, name);
if (typeof proto[name] === "function") {
proto[name] = createMethodSpy(proto[name], proto, config);
} else if (descriptor) {
Object.defineProperty(proto, name, handleDescriptor(name, descriptor, proto));
}
});
Object.getOwnPropertyNames(target).forEach((name) => {
if (name === "length" || name === "prototype" || name === "name") return;
if (typeof target[name] === "function") {
target[name] = createMethodSpy(target[name], target, config);
} else {
const descriptor = Object.getOwnPropertyDescriptor(target, name);
if (descriptor) {
Object.defineProperty(target, name, handleDescriptor(name, descriptor, target));
}
}
});
handleStaticMethodInheritance(target, target, { debug: config.debug });
return target;
}
const MockClass = function(...args) {

@@ -689,11 +763,2 @@ if (!(this instanceof MockClass)) {

}
const processMembers = (target2, source) => {
Object.getOwnPropertyNames(source).forEach((name) => {
if (name === "constructor") return;
if (!target2.hasOwnProperty(name)) {
const descriptor = Object.getOwnPropertyDescriptor(source, name);
Object.defineProperty(target2, name, handleDescriptor(name, descriptor, instance));
}
});
};
processMembers(instance, target.prototype);

@@ -717,3 +782,3 @@ let proto = Object.getPrototypeOf(target.prototype);

});
handleStaticMethodInheritance(MockClass, target, { debug });
handleStaticMethodInheritance(MockClass, target, { debug: config.debug });
return MockClass;

@@ -750,223 +815,73 @@ }

// src/utils/mock-handlers.ts
function isMocked(target) {
return target && typeof target === "object" && "mockClear" in target && "mockReset" in target && "mockRestore" in target;
}
// src/mocks/object.ts
var clone = rfdc__default.default({ circles: true, proto: true });
function shallowClone(obj2) {
const clone2 = Object.create(Object.getPrototypeOf(obj2));
Object.getOwnPropertyNames(obj2).forEach((key) => {
const descriptor = Object.getOwnPropertyDescriptor(obj2, key);
if (descriptor) {
Object.defineProperty(clone2, key, descriptor);
}
});
return clone2;
}
function obj(target, options = {}) {
const processedProperties = /* @__PURE__ */ new Map();
const originalImplementations = /* @__PURE__ */ new Map();
const processedObjects = /* @__PURE__ */ new WeakSet();
const mock3 = {};
const createSpyWithTracking = (fn2, context) => {
const spy = createSpy(fn2);
if (fn2) {
spy.mockImplementation(function(...args) {
return fn2.apply(this === spy ? context : this, args);
if (!target) {
return {};
}
if (isMocked(target)) {
return target;
}
const targetWithMock = options.clone === true ? shallowClone(target) : options.clone === "deep" ? clone(target) : target;
Object.entries(Object.getOwnPropertyDescriptors(target)).forEach(([key, descriptor]) => {
if (typeof descriptor.value === "function") {
const method = descriptor.value;
const spy = createSpy();
const isArrowFn = !method.prototype;
const boundMethod = function(...args) {
return method.apply(isArrowFn ? targetWithMock : this, args);
};
spy.mockImplementation(boundMethod);
Object.defineProperty(targetWithMock, key, {
...descriptor,
value: spy
});
}
return spy;
};
const processValue = (value, key) => {
if (typeof value === "function") {
const spy = createSpyWithTracking(value, mock3);
originalImplementations.set(key, value);
return spy;
} else if (value && typeof value === "object" && !Array.isArray(value)) {
if (processedObjects.has(value)) {
return processedProperties.get(key) || value;
});
let proto = Object.getPrototypeOf(target);
while (proto && proto !== Object.prototype) {
Object.entries(Object.getOwnPropertyDescriptors(proto)).forEach(([key, descriptor]) => {
if (!Object.prototype.hasOwnProperty.call(targetWithMock, key) && typeof descriptor.value === "function") {
const method = descriptor.value;
const spy = createSpy();
const isArrowFn = !method.prototype;
const boundMethod = function(...args) {
return method.apply(isArrowFn ? targetWithMock : this, args);
};
spy.mockImplementation(boundMethod);
Object.defineProperty(targetWithMock, key, {
...descriptor,
value: spy
});
}
processedObjects.add(value);
const nestedMock = {};
Object.entries(value).forEach(([k, v]) => {
if (typeof v === "function") {
const spy = createSpyWithTracking(v, nestedMock);
nestedMock[k] = spy;
processedProperties.set(`${String(key)}.${k}`, spy);
originalImplementations.set(`${String(key)}.${k}`, v);
} else if (v && typeof v === "object" && !Array.isArray(v)) {
nestedMock[k] = processValue(v, `${String(key)}.${k}`);
} else {
nestedMock[k] = v;
}
});
if (options.overrides && typeof options.overrides === "object") {
const mockOverrides = options.overrides[key];
if (mockOverrides && typeof mockOverrides === "object") {
Object.entries(mockOverrides).forEach(([k, v]) => {
if (typeof v === "function") {
if (typeof nestedMock[k] === "function") {
nestedMock[k].mockImplementation(v.bind(nestedMock));
} else {
nestedMock[k] = createSpyWithTracking(v, nestedMock);
processedProperties.set(`${String(key)}.${k}`, nestedMock[k]);
originalImplementations.set(`${String(key)}.${k}`, v);
}
} else if (v && typeof v === "object" && !Array.isArray(v)) {
nestedMock[k] = processValue(v, `${String(key)}.${k}`);
} else {
nestedMock[k] = v;
}
});
}
}
Object.assign(nestedMock, utilityMethods);
processedProperties.set(key, nestedMock);
return nestedMock;
}
return value;
};
const getPropertyDescriptor = (obj2, prop) => {
let current = obj2;
while (current) {
const descriptor = Object.getOwnPropertyDescriptor(current, prop);
if (descriptor) return descriptor;
current = Object.getPrototypeOf(current);
}
return void 0;
};
const utilityMethods = {
mockClear: function() {
processedProperties.forEach((value) => {
if (typeof value === "function" && value.mockClear) {
value.mockClear();
} else if (value && typeof value === "object" && value.mockClear) {
value.mockClear();
}
});
},
mockReset: function() {
processedProperties.forEach((value) => {
if (typeof value === "function" && value.mockReset) {
value.mockReset();
} else if (value && typeof value === "object" && value.mockReset) {
value.mockReset();
}
});
},
mockRestore: function() {
processedProperties.forEach((value, key) => {
if (typeof value === "function") {
if (originalImplementations.has(key)) {
const original = originalImplementations.get(key);
value.mockImplementation(original);
}
} else if (value && typeof value === "object" && value.mockRestore) {
value.mockRestore();
}
});
}
};
if (target) {
Object.entries(target).forEach(([key, value]) => {
if (typeof value === "function") {
processedProperties.set(key, processValue(value, key));
} else if (value && typeof value === "object" && !Array.isArray(value)) {
mock3[key] = processValue(value, key);
} else {
mock3[key] = value;
}
});
let proto = Object.getPrototypeOf(target);
while (proto && proto !== Object.prototype) {
Object.entries(Object.getOwnPropertyDescriptors(proto)).forEach(([key, descriptor]) => {
if (!processedProperties.has(key)) {
if (descriptor.value && typeof descriptor.value === "function") {
processedProperties.set(key, processValue(descriptor.value, key));
} else if (descriptor.get || descriptor.set) {
Object.defineProperty(mock3, key, {
get: descriptor.get?.bind(mock3),
set: descriptor.set?.bind(mock3),
enumerable: descriptor.enumerable,
configurable: true
});
}
}
});
proto = Object.getPrototypeOf(proto);
}
proto = Object.getPrototypeOf(proto);
}
if (options.overrides && typeof options.overrides === "object") {
if (options.overrides) {
Object.entries(options.overrides).forEach(([key, value]) => {
const current = processedProperties.get(key);
if (typeof value === "function" && typeof current === "function" && current.mockImplementation) {
current.mockImplementation(value.bind(mock3));
} else if (value && typeof value === "object" && !Array.isArray(value)) {
mock3[key] = processValue(value, key);
if (typeof value === "function") {
const spy = createSpy();
spy.mockImplementation(value);
targetWithMock[key] = spy;
} else {
processedProperties.set(key, processValue(value, key));
targetWithMock[key] = value;
}
});
}
const handler = {
get(target2, prop) {
if (prop in utilityMethods) {
return utilityMethods[prop];
}
if (processedProperties.has(prop)) {
return processedProperties.get(prop);
}
if (prop in mock3) {
return mock3[prop];
}
if (target2) {
const descriptor = getPropertyDescriptor(target2, prop);
if (descriptor) {
if (typeof descriptor.value === "function") {
const spy2 = processValue(descriptor.value, prop);
processedProperties.set(prop, spy2);
return spy2;
} else if (descriptor.get) {
return descriptor.get.call(mock3);
}
}
}
if (!options.allowUndefined) {
throw new Error(`Property ${String(prop)} is not defined in the original implementation`);
}
const spy = createSpyWithTracking();
processedProperties.set(prop, spy);
return spy;
},
set(target2, prop, value) {
if (options.strict && !(prop in mock3) && !processedProperties.has(prop)) {
throw new Error(`Cannot add property ${String(prop)} in strict mode`);
}
if (typeof value === "function") {
const spy = processValue(value, prop);
processedProperties.set(prop, spy);
mock3[prop] = spy;
} else {
mock3[prop] = value;
}
return true;
},
has(target2, prop) {
return prop in mock3 || processedProperties.has(prop);
},
ownKeys() {
return [.../* @__PURE__ */ new Set([...Object.keys(mock3), ...processedProperties.keys()])];
},
getOwnPropertyDescriptor(target2, prop) {
if (prop in mock3) {
return Object.getOwnPropertyDescriptor(mock3, prop) || {
value: mock3[prop],
writable: true,
enumerable: true,
configurable: true
};
}
if (processedProperties.has(prop)) {
return {
value: processedProperties.get(prop),
writable: true,
enumerable: true,
configurable: true
};
}
return {
value: void 0,
writable: true,
enumerable: true,
configurable: true
};
}
};
return new Proxy(target || {}, handler);
return targetWithMock;
}

@@ -981,8 +896,4 @@

const config = {
allowUndefined: options.allowUndefined ?? false,
strict: options.strict ?? false,
trackCalls: options.trackCalls ?? false,
debug: options.debug ?? false,
preservePrototype: options.preservePrototype ?? true,
timeout: options.timeout ?? 0
...DEFAULT_FUNCTION_OPTIONS,
...options
};

@@ -1039,22 +950,29 @@ if (!(key in obj2)) {

function compose(target, options = {}) {
if (target === void 0) {
if (!target) {
return fn();
}
if (typeof target === "function" && "prototype" in target) {
const classTarget = target;
return cls(classTarget, options);
if (typeof target === "function") {
const config2 = {
...DEFAULT_CLASS_OPTIONS,
...options,
overrides: {
...DEFAULT_CLASS_OPTIONS.overrides,
...options.overrides
}
};
return cls(target, config2);
}
const mockObj = obj(target, options);
const config = {
...DEFAULT_OBJECT_OPTIONS,
...options
};
const result = obj(target, config);
if (options.replace) {
for (const [key, impl] of Object.entries(options.replace)) {
if (!(key in mockObj)) {
throw new Error(`Cannot replace non-existent method: ${key}`);
Object.entries(options.replace).forEach(([key, impl]) => {
if (impl && typeof impl === "function") {
replace(result, key, impl);
}
if (typeof impl !== "function") {
throw new Error(`Replacement for method ${key} must be a function`);
}
replace(mockObj, key, impl);
}
});
}
return mockObj;
return result;
}

@@ -1183,18 +1101,10 @@

var MockImpl = class extends MockRegistry {
constructor(config = {}) {
constructor(options = {}) {
super();
this.currentConfig = { ...DEFAULT_CONFIG, ...config };
this.currentOptions = { ...DEFAULT_BASE_OPTIONS, ...options };
}
configure(config) {
if (!config) {
return { ...this.currentConfig };
}
Object.assign(this.currentConfig, config);
}
/**
* Create a mock function with tracking capabilities
* @param options Optional configuration overrides
* @returns Mocked function with spy features
* Creates a mock function
*/
fn(options) {
fn(_options) {
const mockFn = fn();

@@ -1205,9 +1115,7 @@ this.registerMock(mockFn, "function");

/**
* 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
* Creates a mock object
*/
obj(target, options = {}) {
const mockObj = obj(target, { ...this.currentConfig, ...options });
const config = createObjectOptions({ ...this.currentOptions, ...options });
const mockObj = obj(target, config);
this.registerMock(mockObj, "object", target);

@@ -1217,9 +1125,7 @@ return mockObj;

/**
* Create a mock class with tracking capabilities
* @param target Original class to mock
* @param options Mock configuration and implementation
* @returns Mocked class constructor
* Creates a mock class with optional implementation
*/
cls(target, options) {
const mockCls = cls(target, { ...this.currentConfig, ...options });
const config = createClassOptions({ ...this.currentOptions, ...options });
const mockCls = cls(target, config);
this.registerMock(mockCls, "class", target);

@@ -1229,14 +1135,16 @@ return mockCls;

/**
* Create a mock from partial implementation
* @param partial Partial implementation to mock
* @param options Mock configuration
* @returns Complete mock object
* Casts a partial implementation to a complete mock
*/
cast(partial, options) {
const mockObj = cast(partial, { ...this.currentConfig, ...options });
const config = createCastOptions({ ...this.currentOptions, ...options });
const mockObj = cast(partial, config);
this.registerMock(mockObj, "object", partial);
return mockObj;
}
compose(target, options) {
const mockObj = compose(target, { ...this.currentConfig, ...options });
/**
* Creates a mock from a class constructor or object
*/
compose(target, _options = {}) {
const config = target instanceof Function ? createClassOptions({ ...this.currentOptions, ..._options }) : createObjectOptions({ ...this.currentOptions, ..._options });
const mockObj = compose(target, config);
this.registerMock(mockObj, typeof target === "function" ? "class" : "object", target);

@@ -1246,10 +1154,7 @@ return mockObj;

/**
* Replace a method with mock implementation
* @param obj Target object
* @param key Method key to replace
* @param impl Mock implementation
* @param options Mock configuration
* Replaces a method with mock implementation
*/
replace(obj2, key, impl, options) {
replace(obj2, key, impl, { ...this.currentConfig, ...options });
const config = createFunctionOptions({ ...this.currentOptions, ...options });
replace(obj2, key, impl, config);
}

@@ -1259,4 +1164,4 @@ };

var mock = Object.assign(
function mock2(config) {
return new MockImpl(config);
function mock2(options) {
return new MockImpl(options);
},

@@ -1269,23 +1174,14 @@ {

replace: (obj2, key, impl, options) => globalMock.replace(obj2, key, impl, options),
compose: (target, options) => globalMock.compose(target, options),
configure: function(config) {
if (!config) {
return globalMock.configure();
}
globalMock.configure(config);
}
compose: (target, _options = {}) => globalMock.compose(target, _options)
}
);
var mock_default = mock;
exports.DEFAULT_CONFIG = DEFAULT_CONFIG;
exports.cast = cast;
exports.cls = cls;
exports.compose = compose;
exports.createPropertySpy = createPropertySpy;
exports.createSpy = createSpy;
exports.fn = fn;
exports.mock = mock;
exports.obj = obj;
exports.replace = replace;
exports.DEFAULT_BASE_OPTIONS = DEFAULT_BASE_OPTIONS;
exports.DEFAULT_CAST_OPTIONS = DEFAULT_CAST_OPTIONS;
exports.DEFAULT_CLASS_OPTIONS = DEFAULT_CLASS_OPTIONS;
exports.DEFAULT_FUNCTION_OPTIONS = DEFAULT_FUNCTION_OPTIONS;
exports.DEFAULT_OBJECT_OPTIONS = DEFAULT_OBJECT_OPTIONS;
exports.mock = mock_default;
//# sourceMappingURL=index.js.map
//# sourceMappingURL=index.js.map

@@ -1,10 +0,52 @@

// src/config.ts
var DEFAULT_CONFIG = {
allowUndefined: false,
strict: false,
import rfdc from 'rfdc';
// src/defaults.ts
var DEFAULT_BASE_OPTIONS = {
debug: false
};
var DEFAULT_CIRCULAR_OPTIONS = {
handleCircular: true
};
var DEFAULT_FUNCTION_OPTIONS = {
...DEFAULT_BASE_OPTIONS,
trackCalls: false,
debug: false,
timeout: 5e3,
preservePrototype: true
autoSpy: false
};
var DEFAULT_OBJECT_OPTIONS = {
...DEFAULT_BASE_OPTIONS,
...DEFAULT_CIRCULAR_OPTIONS,
mockPrototype: false,
overrides: {}
};
var DEFAULT_CLASS_OPTIONS = {
...DEFAULT_OBJECT_OPTIONS,
preservePrototype: true,
preserveConstructor: true,
mockStaticMembers: false,
mockInPlace: false
};
var DEFAULT_CAST_OPTIONS = {
...DEFAULT_BASE_OPTIONS,
...DEFAULT_CIRCULAR_OPTIONS,
keepPrototype: true,
asyncTimeout: 5e3
};
function createOptions(defaults, options = {}) {
return {
...defaults,
...options
};
}
function createFunctionOptions(options = {}) {
return createOptions(DEFAULT_FUNCTION_OPTIONS, options);
}
function createObjectOptions(options = {}) {
return createOptions(DEFAULT_OBJECT_OPTIONS, options);
}
function createClassOptions(options = {}) {
return createOptions(DEFAULT_CLASS_OPTIONS, options);
}
function createCastOptions(options = {}) {
return createOptions(DEFAULT_CAST_OPTIONS, options);
}

@@ -14,8 +56,4 @@ // src/mocks/cast.ts

const config = {
allowUndefined: options.allowUndefined ?? false,
strict: options.strict ?? false,
trackCalls: options.trackCalls ?? false,
debug: options.debug ?? false,
preservePrototype: options.preservePrototype ?? true,
timeout: options.timeout ?? 5e3
...DEFAULT_CAST_OPTIONS,
...options
};

@@ -39,3 +77,3 @@ const proxies = /* @__PURE__ */ new WeakMap();

}
if (prop === "nested" && !config.strict && !config.allowUndefined) {
if (prop === "nested") {
const hasCalculate = target && typeof target.calculate === "function";

@@ -71,3 +109,3 @@ if (hasCalculate) {

get: (target, prop) => {
if (config.timeout > 0 && Date.now() - startTime > config.timeout) {
if (config.asyncTimeout && Date.now() - startTime > config.asyncTimeout) {
throw new Error("Cast operation timed out");

@@ -79,5 +117,5 @@ }

if (prop === Symbol.hasInstance) {
return config.preservePrototype ? target[Symbol.hasInstance] : void 0;
return config.keepPrototype ? target[Symbol.hasInstance] : void 0;
}
if (!config.preservePrototype) {
if (!config.keepPrototype) {
if (prop === "__proto__" || prop === "constructor") {

@@ -134,6 +172,3 @@ return Object.getPrototypeOf({});

}
if (config.strict) {
throw new Error(`Property ${String(prop)} is not defined`);
}
if (config.allowUndefined || !shouldBeObject(target, prop)) {
if (!shouldBeObject(target, prop)) {
return void 0;

@@ -153,3 +188,3 @@ }

getPrototypeOf: (target) => {
return config.preservePrototype ? Object.getPrototypeOf(target) : Object.prototype;
return config.keepPrototype ? Object.getPrototypeOf(target) : Object.prototype;
},

@@ -450,3 +485,3 @@ ownKeys: (target) => {

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

@@ -559,2 +594,5 @@ result = void 0;

});
if (implementation) {
this.updateState({ implementation });
}
this.spyFunction = boundSpy;

@@ -591,11 +629,9 @@ }

}
function createPropertySpy() {
return {
get: createSpy(),
set: createSpy()
};
}
// src/utils/method-spy.ts
function createMethodSpy(method, context, options = {}) {
({
...DEFAULT_FUNCTION_OPTIONS,
...options
});
const spy = new UniversalSpy();

@@ -609,8 +645,2 @@ const spyFn = spy.getSpy();

}
function createPropertyMethodSpy(getter, setter, context, options = {}) {
return {
get: getter ? createMethodSpy(getter, context, options) : void 0,
set: setter ? createMethodSpy(setter, context, options) : void 0
};
}

@@ -631,4 +661,7 @@ // src/mocks/class.ts

function cls(target, options = {}) {
const { overrides: implementation = {}, debug, trackCalls } = options;
if (debug) {
const config = {
...DEFAULT_CLASS_OPTIONS,
...options
};
if (config.debug) {
console.debug("Creating class mock for:", target.name);

@@ -639,39 +672,76 @@ }

return {
value: void 0,
configurable: true,
enumerable: true,
writable: true,
configurable: true,
enumerable: true
value: void 0
};
}
if (descriptor.get || descriptor.set) {
const implDescriptor = typeof name === "string" ? Object.getOwnPropertyDescriptor(implementation, name) : void 0;
const spies = createPropertyMethodSpy(
implDescriptor?.get || descriptor.get,
implDescriptor?.set || descriptor.set,
context,
{ debug }
);
const { value, get, set } = descriptor;
if (typeof value === "function") {
const override = typeof name === "string" && config.overrides?.[name];
if (override) {
return {
...descriptor,
value: createMethodSpy(override, context, config)
};
}
return {
configurable: true,
enumerable: true,
get: spies.get || descriptor.get?.bind(context),
set: spies.set || descriptor.set?.bind(context)
...descriptor,
value: config.preservePrototype ? createMethodSpy(value, context, config) : createSpy()
};
}
if (typeof descriptor.value === "function") {
const impl = typeof name === "string" ? implementation[name] : void 0;
if (impl) {
const spy = createMethodSpy(impl, context, { debug });
return { ...descriptor, value: spy };
}
if (get || set) {
const getterOverride = typeof name === "string" && config.overrides?.[`get${name.charAt(0).toUpperCase()}${name.slice(1)}`];
const setterOverride = typeof name === "string" && config.overrides?.[`set${name.charAt(0).toUpperCase()}${name.slice(1)}`];
const getterSpy = get && (getterOverride ? createMethodSpy(getterOverride, context, config) : config.preservePrototype ? createMethodSpy(get, context, config) : createSpy());
const setterSpy = set && (setterOverride ? createMethodSpy(setterOverride, context, config) : config.preservePrototype ? createMethodSpy(set, context, config) : createSpy());
return {
...descriptor,
value: createMethodSpy(descriptor.value, context, { debug })
get: getterSpy,
set: setterSpy
};
}
return {
...descriptor,
value: descriptor.value
};
return descriptor;
};
const processMembers = (target2, source) => {
Object.getOwnPropertyNames(source).forEach((name) => {
if (name === "constructor") return;
const descriptor = Object.getOwnPropertyDescriptor(source, name);
const newDescriptor = handleDescriptor(name, descriptor, target2);
if (!config.mockInPlace || !target2.hasOwnProperty(name)) {
Object.defineProperty(target2, name, newDescriptor);
} else {
if (typeof source[name] === "function") {
target2[name] = createMethodSpy(source[name], target2, config);
} else {
Object.defineProperty(target2, name, newDescriptor);
}
}
});
};
if (config.mockInPlace) {
const proto = target.prototype;
Object.getOwnPropertyNames(proto).forEach((name) => {
if (name === "constructor") return;
const descriptor = Object.getOwnPropertyDescriptor(proto, name);
if (typeof proto[name] === "function") {
proto[name] = createMethodSpy(proto[name], proto, config);
} else if (descriptor) {
Object.defineProperty(proto, name, handleDescriptor(name, descriptor, proto));
}
});
Object.getOwnPropertyNames(target).forEach((name) => {
if (name === "length" || name === "prototype" || name === "name") return;
if (typeof target[name] === "function") {
target[name] = createMethodSpy(target[name], target, config);
} else {
const descriptor = Object.getOwnPropertyDescriptor(target, name);
if (descriptor) {
Object.defineProperty(target, name, handleDescriptor(name, descriptor, target));
}
}
});
handleStaticMethodInheritance(target, target, { debug: config.debug });
return target;
}
const MockClass = function(...args) {

@@ -687,11 +757,2 @@ if (!(this instanceof MockClass)) {

}
const processMembers = (target2, source) => {
Object.getOwnPropertyNames(source).forEach((name) => {
if (name === "constructor") return;
if (!target2.hasOwnProperty(name)) {
const descriptor = Object.getOwnPropertyDescriptor(source, name);
Object.defineProperty(target2, name, handleDescriptor(name, descriptor, instance));
}
});
};
processMembers(instance, target.prototype);

@@ -715,3 +776,3 @@ let proto = Object.getPrototypeOf(target.prototype);

});
handleStaticMethodInheritance(MockClass, target, { debug });
handleStaticMethodInheritance(MockClass, target, { debug: config.debug });
return MockClass;

@@ -748,223 +809,73 @@ }

// src/utils/mock-handlers.ts
function isMocked(target) {
return target && typeof target === "object" && "mockClear" in target && "mockReset" in target && "mockRestore" in target;
}
// src/mocks/object.ts
var clone = rfdc({ circles: true, proto: true });
function shallowClone(obj2) {
const clone2 = Object.create(Object.getPrototypeOf(obj2));
Object.getOwnPropertyNames(obj2).forEach((key) => {
const descriptor = Object.getOwnPropertyDescriptor(obj2, key);
if (descriptor) {
Object.defineProperty(clone2, key, descriptor);
}
});
return clone2;
}
function obj(target, options = {}) {
const processedProperties = /* @__PURE__ */ new Map();
const originalImplementations = /* @__PURE__ */ new Map();
const processedObjects = /* @__PURE__ */ new WeakSet();
const mock3 = {};
const createSpyWithTracking = (fn2, context) => {
const spy = createSpy(fn2);
if (fn2) {
spy.mockImplementation(function(...args) {
return fn2.apply(this === spy ? context : this, args);
if (!target) {
return {};
}
if (isMocked(target)) {
return target;
}
const targetWithMock = options.clone === true ? shallowClone(target) : options.clone === "deep" ? clone(target) : target;
Object.entries(Object.getOwnPropertyDescriptors(target)).forEach(([key, descriptor]) => {
if (typeof descriptor.value === "function") {
const method = descriptor.value;
const spy = createSpy();
const isArrowFn = !method.prototype;
const boundMethod = function(...args) {
return method.apply(isArrowFn ? targetWithMock : this, args);
};
spy.mockImplementation(boundMethod);
Object.defineProperty(targetWithMock, key, {
...descriptor,
value: spy
});
}
return spy;
};
const processValue = (value, key) => {
if (typeof value === "function") {
const spy = createSpyWithTracking(value, mock3);
originalImplementations.set(key, value);
return spy;
} else if (value && typeof value === "object" && !Array.isArray(value)) {
if (processedObjects.has(value)) {
return processedProperties.get(key) || value;
});
let proto = Object.getPrototypeOf(target);
while (proto && proto !== Object.prototype) {
Object.entries(Object.getOwnPropertyDescriptors(proto)).forEach(([key, descriptor]) => {
if (!Object.prototype.hasOwnProperty.call(targetWithMock, key) && typeof descriptor.value === "function") {
const method = descriptor.value;
const spy = createSpy();
const isArrowFn = !method.prototype;
const boundMethod = function(...args) {
return method.apply(isArrowFn ? targetWithMock : this, args);
};
spy.mockImplementation(boundMethod);
Object.defineProperty(targetWithMock, key, {
...descriptor,
value: spy
});
}
processedObjects.add(value);
const nestedMock = {};
Object.entries(value).forEach(([k, v]) => {
if (typeof v === "function") {
const spy = createSpyWithTracking(v, nestedMock);
nestedMock[k] = spy;
processedProperties.set(`${String(key)}.${k}`, spy);
originalImplementations.set(`${String(key)}.${k}`, v);
} else if (v && typeof v === "object" && !Array.isArray(v)) {
nestedMock[k] = processValue(v, `${String(key)}.${k}`);
} else {
nestedMock[k] = v;
}
});
if (options.overrides && typeof options.overrides === "object") {
const mockOverrides = options.overrides[key];
if (mockOverrides && typeof mockOverrides === "object") {
Object.entries(mockOverrides).forEach(([k, v]) => {
if (typeof v === "function") {
if (typeof nestedMock[k] === "function") {
nestedMock[k].mockImplementation(v.bind(nestedMock));
} else {
nestedMock[k] = createSpyWithTracking(v, nestedMock);
processedProperties.set(`${String(key)}.${k}`, nestedMock[k]);
originalImplementations.set(`${String(key)}.${k}`, v);
}
} else if (v && typeof v === "object" && !Array.isArray(v)) {
nestedMock[k] = processValue(v, `${String(key)}.${k}`);
} else {
nestedMock[k] = v;
}
});
}
}
Object.assign(nestedMock, utilityMethods);
processedProperties.set(key, nestedMock);
return nestedMock;
}
return value;
};
const getPropertyDescriptor = (obj2, prop) => {
let current = obj2;
while (current) {
const descriptor = Object.getOwnPropertyDescriptor(current, prop);
if (descriptor) return descriptor;
current = Object.getPrototypeOf(current);
}
return void 0;
};
const utilityMethods = {
mockClear: function() {
processedProperties.forEach((value) => {
if (typeof value === "function" && value.mockClear) {
value.mockClear();
} else if (value && typeof value === "object" && value.mockClear) {
value.mockClear();
}
});
},
mockReset: function() {
processedProperties.forEach((value) => {
if (typeof value === "function" && value.mockReset) {
value.mockReset();
} else if (value && typeof value === "object" && value.mockReset) {
value.mockReset();
}
});
},
mockRestore: function() {
processedProperties.forEach((value, key) => {
if (typeof value === "function") {
if (originalImplementations.has(key)) {
const original = originalImplementations.get(key);
value.mockImplementation(original);
}
} else if (value && typeof value === "object" && value.mockRestore) {
value.mockRestore();
}
});
}
};
if (target) {
Object.entries(target).forEach(([key, value]) => {
if (typeof value === "function") {
processedProperties.set(key, processValue(value, key));
} else if (value && typeof value === "object" && !Array.isArray(value)) {
mock3[key] = processValue(value, key);
} else {
mock3[key] = value;
}
});
let proto = Object.getPrototypeOf(target);
while (proto && proto !== Object.prototype) {
Object.entries(Object.getOwnPropertyDescriptors(proto)).forEach(([key, descriptor]) => {
if (!processedProperties.has(key)) {
if (descriptor.value && typeof descriptor.value === "function") {
processedProperties.set(key, processValue(descriptor.value, key));
} else if (descriptor.get || descriptor.set) {
Object.defineProperty(mock3, key, {
get: descriptor.get?.bind(mock3),
set: descriptor.set?.bind(mock3),
enumerable: descriptor.enumerable,
configurable: true
});
}
}
});
proto = Object.getPrototypeOf(proto);
}
proto = Object.getPrototypeOf(proto);
}
if (options.overrides && typeof options.overrides === "object") {
if (options.overrides) {
Object.entries(options.overrides).forEach(([key, value]) => {
const current = processedProperties.get(key);
if (typeof value === "function" && typeof current === "function" && current.mockImplementation) {
current.mockImplementation(value.bind(mock3));
} else if (value && typeof value === "object" && !Array.isArray(value)) {
mock3[key] = processValue(value, key);
if (typeof value === "function") {
const spy = createSpy();
spy.mockImplementation(value);
targetWithMock[key] = spy;
} else {
processedProperties.set(key, processValue(value, key));
targetWithMock[key] = value;
}
});
}
const handler = {
get(target2, prop) {
if (prop in utilityMethods) {
return utilityMethods[prop];
}
if (processedProperties.has(prop)) {
return processedProperties.get(prop);
}
if (prop in mock3) {
return mock3[prop];
}
if (target2) {
const descriptor = getPropertyDescriptor(target2, prop);
if (descriptor) {
if (typeof descriptor.value === "function") {
const spy2 = processValue(descriptor.value, prop);
processedProperties.set(prop, spy2);
return spy2;
} else if (descriptor.get) {
return descriptor.get.call(mock3);
}
}
}
if (!options.allowUndefined) {
throw new Error(`Property ${String(prop)} is not defined in the original implementation`);
}
const spy = createSpyWithTracking();
processedProperties.set(prop, spy);
return spy;
},
set(target2, prop, value) {
if (options.strict && !(prop in mock3) && !processedProperties.has(prop)) {
throw new Error(`Cannot add property ${String(prop)} in strict mode`);
}
if (typeof value === "function") {
const spy = processValue(value, prop);
processedProperties.set(prop, spy);
mock3[prop] = spy;
} else {
mock3[prop] = value;
}
return true;
},
has(target2, prop) {
return prop in mock3 || processedProperties.has(prop);
},
ownKeys() {
return [.../* @__PURE__ */ new Set([...Object.keys(mock3), ...processedProperties.keys()])];
},
getOwnPropertyDescriptor(target2, prop) {
if (prop in mock3) {
return Object.getOwnPropertyDescriptor(mock3, prop) || {
value: mock3[prop],
writable: true,
enumerable: true,
configurable: true
};
}
if (processedProperties.has(prop)) {
return {
value: processedProperties.get(prop),
writable: true,
enumerable: true,
configurable: true
};
}
return {
value: void 0,
writable: true,
enumerable: true,
configurable: true
};
}
};
return new Proxy(target || {}, handler);
return targetWithMock;
}

@@ -979,8 +890,4 @@

const config = {
allowUndefined: options.allowUndefined ?? false,
strict: options.strict ?? false,
trackCalls: options.trackCalls ?? false,
debug: options.debug ?? false,
preservePrototype: options.preservePrototype ?? true,
timeout: options.timeout ?? 0
...DEFAULT_FUNCTION_OPTIONS,
...options
};

@@ -1037,22 +944,29 @@ if (!(key in obj2)) {

function compose(target, options = {}) {
if (target === void 0) {
if (!target) {
return fn();
}
if (typeof target === "function" && "prototype" in target) {
const classTarget = target;
return cls(classTarget, options);
if (typeof target === "function") {
const config2 = {
...DEFAULT_CLASS_OPTIONS,
...options,
overrides: {
...DEFAULT_CLASS_OPTIONS.overrides,
...options.overrides
}
};
return cls(target, config2);
}
const mockObj = obj(target, options);
const config = {
...DEFAULT_OBJECT_OPTIONS,
...options
};
const result = obj(target, config);
if (options.replace) {
for (const [key, impl] of Object.entries(options.replace)) {
if (!(key in mockObj)) {
throw new Error(`Cannot replace non-existent method: ${key}`);
Object.entries(options.replace).forEach(([key, impl]) => {
if (impl && typeof impl === "function") {
replace(result, key, impl);
}
if (typeof impl !== "function") {
throw new Error(`Replacement for method ${key} must be a function`);
}
replace(mockObj, key, impl);
}
});
}
return mockObj;
return result;
}

@@ -1181,18 +1095,10 @@

var MockImpl = class extends MockRegistry {
constructor(config = {}) {
constructor(options = {}) {
super();
this.currentConfig = { ...DEFAULT_CONFIG, ...config };
this.currentOptions = { ...DEFAULT_BASE_OPTIONS, ...options };
}
configure(config) {
if (!config) {
return { ...this.currentConfig };
}
Object.assign(this.currentConfig, config);
}
/**
* Create a mock function with tracking capabilities
* @param options Optional configuration overrides
* @returns Mocked function with spy features
* Creates a mock function
*/
fn(options) {
fn(_options) {
const mockFn = fn();

@@ -1203,9 +1109,7 @@ this.registerMock(mockFn, "function");

/**
* 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
* Creates a mock object
*/
obj(target, options = {}) {
const mockObj = obj(target, { ...this.currentConfig, ...options });
const config = createObjectOptions({ ...this.currentOptions, ...options });
const mockObj = obj(target, config);
this.registerMock(mockObj, "object", target);

@@ -1215,9 +1119,7 @@ return mockObj;

/**
* Create a mock class with tracking capabilities
* @param target Original class to mock
* @param options Mock configuration and implementation
* @returns Mocked class constructor
* Creates a mock class with optional implementation
*/
cls(target, options) {
const mockCls = cls(target, { ...this.currentConfig, ...options });
const config = createClassOptions({ ...this.currentOptions, ...options });
const mockCls = cls(target, config);
this.registerMock(mockCls, "class", target);

@@ -1227,14 +1129,16 @@ return mockCls;

/**
* Create a mock from partial implementation
* @param partial Partial implementation to mock
* @param options Mock configuration
* @returns Complete mock object
* Casts a partial implementation to a complete mock
*/
cast(partial, options) {
const mockObj = cast(partial, { ...this.currentConfig, ...options });
const config = createCastOptions({ ...this.currentOptions, ...options });
const mockObj = cast(partial, config);
this.registerMock(mockObj, "object", partial);
return mockObj;
}
compose(target, options) {
const mockObj = compose(target, { ...this.currentConfig, ...options });
/**
* Creates a mock from a class constructor or object
*/
compose(target, _options = {}) {
const config = target instanceof Function ? createClassOptions({ ...this.currentOptions, ..._options }) : createObjectOptions({ ...this.currentOptions, ..._options });
const mockObj = compose(target, config);
this.registerMock(mockObj, typeof target === "function" ? "class" : "object", target);

@@ -1244,10 +1148,7 @@ return mockObj;

/**
* Replace a method with mock implementation
* @param obj Target object
* @param key Method key to replace
* @param impl Mock implementation
* @param options Mock configuration
* Replaces a method with mock implementation
*/
replace(obj2, key, impl, options) {
replace(obj2, key, impl, { ...this.currentConfig, ...options });
const config = createFunctionOptions({ ...this.currentOptions, ...options });
replace(obj2, key, impl, config);
}

@@ -1257,4 +1158,4 @@ };

var mock = Object.assign(
function mock2(config) {
return new MockImpl(config);
function mock2(options) {
return new MockImpl(options);
},

@@ -1267,14 +1168,9 @@ {

replace: (obj2, key, impl, options) => globalMock.replace(obj2, key, impl, options),
compose: (target, options) => globalMock.compose(target, options),
configure: function(config) {
if (!config) {
return globalMock.configure();
}
globalMock.configure(config);
}
compose: (target, _options = {}) => globalMock.compose(target, _options)
}
);
var mock_default = mock;
export { DEFAULT_CONFIG, cast, cls, compose, createPropertySpy, createSpy, fn, mock, obj, replace };
export { DEFAULT_BASE_OPTIONS, DEFAULT_CAST_OPTIONS, DEFAULT_CLASS_OPTIONS, DEFAULT_FUNCTION_OPTIONS, DEFAULT_OBJECT_OPTIONS, mock_default as mock };
//# sourceMappingURL=index.js.map
//# sourceMappingURL=index.js.map
{
"name": "@corez/mock",
"version": "0.2.5",
"version": "0.3.0",
"description": "A powerful and flexible TypeScript mocking library for testing",

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

"devDependencies": {
"@eslint/js": "^9.18.0",
"@release-it/conventional-changelog": "^9.0.4",

@@ -65,2 +66,3 @@ "@types/jest": "^29.5.14",

"eslint-plugin-simple-import-sort": "^12.1.1",
"eslint-plugin-unused-imports": "^4.1.4",
"jest": "^29.7.0",

@@ -99,3 +101,6 @@ "prettier": "^3.4.2",

}
},
"dependencies": {
"rfdc": "^1.4.1"
}
}

@@ -29,4 +29,4 @@ # @corez/mock

- 🎮 **Intuitive API** - Clean and chainable API design
- 🛡️ **Strict Mode** - Optional strict mode for rigorous testing
- 🔍 **Debug Support** - Detailed logging for troubleshooting
- 🛡 **Debug Support** - Detailed logging for troubleshooting
- 🔄 **In-Place Mocking** - Option to modify original classes with restore capability

@@ -83,3 +83,4 @@ ## Installation

const db = mock.cls(Database, {
// Create a new mock class
const MockDatabase = mock.cls(Database, {
overrides: {

@@ -89,2 +90,10 @@ query: async () => [{id: 1}],

});
// Or modify the original class
mock.cls(Database, {
mockInPlace: true,
overrides: {
query: async () => [{id: 1}],
},
});
```

@@ -158,2 +167,3 @@

// Create a new mock class
const MockDatabase = mock.cls(Database, {

@@ -169,2 +179,18 @@ overrides: {

console.log(queryMock.calls.count());
// Or modify the original class
mock.cls(Database, {
mockInPlace: true,
overrides: {
query: async () => [{id: 1}],
},
});
// Store original methods for restoration
const originalQuery = Database.prototype.query;
const originalConnect = Database.prototype.connect;
// Later, restore original methods
Database.prototype.query = originalQuery;
Database.prototype.connect = originalConnect;
```

@@ -244,2 +270,3 @@

// Create a new mock class
const MockDatabase = mock.cls(Database, {

@@ -256,5 +283,17 @@ overrides: {

// Verify method calls
expect(queryMock.calls.count()).toBe(1);
expect(queryMock.calls.all()[0].args).toEqual(['SELECT * FROM users']);
// Or modify the original class
mock.cls(Database, {
mockInPlace: true,
overrides: {
query: async () => [{id: 1}],
},
});
// Store original methods for restoration
const originalQuery = Database.prototype.query;
const originalConnect = Database.prototype.connect;
// Later, restore original methods
Database.prototype.query = originalQuery;
Database.prototype.connect = originalConnect;
```

@@ -403,11 +442,2 @@

### Strict Mode
Enable strict mode for rigorous testing:
```typescript
const strict = mock.obj<Service>({}, {strict: true});
strict.unknownMethod(); // Throws error
```
### Async Mocking

@@ -528,1 +558,118 @@

Apache-2.0 - see [LICENSE](LICENSE) for details.
## Mock Options
### Base Options
All mock types support these basic options:
- `debug`: Enables detailed logging of mock operations (default: false)
### Function Mock Options
Options for mocking functions:
- `trackCalls`: Records arguments, return values, and call count (default: false)
- `autoSpy`: Automatically creates spies for all function properties (default: false)
### Object Mock Options
Options for mocking objects:
- `handleCircular`: Handles circular references in complex objects (default: true)
- `mockPrototype`: Controls whether to mock methods from the prototype chain (default: false)
- `overrides`: Allows overriding specific properties or methods with custom implementations
### Class Mock Options
Options for mocking classes (extends Object Mock Options):
- `mockInPlace`: Modifies the original class instead of creating a new one (default: false)
- `preservePrototype`: Maintains the original prototype chain (default: true)
- `preserveConstructor`: Preserves original constructor behavior (default: true)
- `mockStaticMembers`: Controls mocking of static class members (default: false)
### Cast Mock Options
Options for type casting and partial implementations:
- `handleCircular`: Handles circular references (default: true)
- `keepPrototype`: Maintains the prototype chain when casting (default: true)
- `asyncTimeout`: Maximum time to wait for async operations in ms (default: 5000)
## Usage Examples
### Basic Function Mocking
```typescript
import {mock} from 'hexxspark';
// Create a mock function
const mockFn = mock.fn<() => string>();
mockFn.mockReturnValue('hello');
// Track calls
expect(mockFn()).toBe('hello');
expect(mockFn.mock.calls.length).toBe(1);
```
### Object Mocking
```typescript
import {mock} from 'hexxspark';
interface User {
name: string;
getId(): number;
}
// Create a mock object
const mockUser = mock.obj<User>({
name: 'Test User',
getId: () => 1,
});
// Override methods
mockUser.getId.mockReturnValue(2);
```
### Class Mocking
```typescript
import {mock} from 'hexxspark';
class Database {
connect() {
/* ... */
}
query(sql: string) {
/* ... */
}
}
// Create a mock class
const MockDatabase = mock.cls(Database, {
mockStaticMembers: true,
preserveConstructor: true,
});
const db = new MockDatabase();
db.query.mockResolvedValue({rows: []});
```
### Type Casting
```typescript
import {mock} from 'hexxspark';
interface ComplexType {
data: string;
process(): Promise<void>;
}
// Cast partial implementation to full mock
const partial = {data: 'test'};
const mockObj = mock.cast<ComplexType>(partial, {
asyncTimeout: 1000,
});
```

@@ -1,57 +0,11 @@

/**
* Mock configuration options
*/
export interface Config {
/**
* Whether to allow accessing undefined properties
* Used in object mocking to control property access
* @default false
*/
allowUndefined: boolean;
/**
* Whether to use strict mode
* In strict mode, adding new properties to mock objects is not allowed
* @default false
*/
strict: boolean;
/**
* Whether to track method calls
* When enabled, creates a MethodTracker to record call history
* @default false
*/
trackCalls: boolean;
/**
* Whether to enable debug mode
* When enabled, outputs debug logs for mock creation and interactions
* @default false
*/
debug: boolean;
/**
* Default timeout for async operations in milliseconds
* Used in async mocks, promise wrappers, and retry mechanisms
* @default 5000
*/
preservePrototype?: boolean;
mockPrototype?: boolean;
timeout: number;
/**
* Whether to preserve the prototype chain
* @default true
*/
preservePrototype: boolean;
}
/**
* Default configuration values
*/
export const DEFAULT_CONFIG: Config = {
allowUndefined: false,
strict: false,
trackCalls: false,
debug: false,
preservePrototype: true,
mockPrototype: false,
timeout: 5000,
preservePrototype: true,
};
// Core functionality
export {mock} from './mock';
export {createPropertySpy, createSpy} from './spy';
// Configuration
export type {Config} from './config';
export {DEFAULT_CONFIG} from './config';
// Types
export type {
Adapter,
AsyncFn,
export {
DEFAULT_BASE_OPTIONS,
DEFAULT_CAST_OPTIONS,
DEFAULT_CLASS_OPTIONS,
DEFAULT_FUNCTION_OPTIONS,
DEFAULT_OBJECT_OPTIONS,
} from './defaults';
export {default as mock} from './mock';
export {
BaseMockOptions,
CastMockOptions,
ClassMockOptions,
ClsMock,
ClsMockOptions,
Constructor,
DeepMock,
DeepPartial,
Fn,
Framework,
Matcher,
MatcherFactory,
FunctionMockOptions,
Mock,
MockClassOptions,
MockFunction,
MockObject,
MockObjectOptions,
MockOf,
ObjMockOptions,
PartialBuilder,
PartialOptions,
SpyAdapter,
StaticMockOf,
ObjectMockOptions,
} from './types';
// Utility functions
export {cast, cls, compose, fn, obj, replace} from './mocks';

@@ -1,14 +0,23 @@

import {Config, DEFAULT_CONFIG} from './config';
import {
createCastOptions,
createClassOptions,
createFunctionOptions,
createObjectOptions,
DEFAULT_BASE_OPTIONS,
} from './defaults';
import {cast, cls, compose, fn, obj, replace} from './mocks';
import {MockRegistry} from './registry';
import {
BaseMockOptions,
CastMockOptions,
ClassMockOptions,
ClsMock,
ClsMockOptions,
Constructor,
DeepPartial,
Fn,
FunctionMockOptions,
Mock,
MockFunction,
MockObject,
ObjMockOptions,
ObjectMockOptions,
} from './types';

@@ -21,29 +30,13 @@

class MockImpl extends MockRegistry implements Mock {
private currentConfig: Config;
private currentOptions: BaseMockOptions;
constructor(config: Partial<Config> = {}) {
constructor(options: Partial<BaseMockOptions> = {}) {
super();
this.currentConfig = {...DEFAULT_CONFIG, ...config};
this.currentOptions = {...DEFAULT_BASE_OPTIONS, ...options};
}
/**
* Get or set mock configuration
* @param config Optional configuration to update
* @returns Current configuration when called without arguments
* Creates a mock function
*/
configure(): Config;
configure(config: Partial<Config>): void;
configure(config?: Partial<Config>): Config | void {
if (!config) {
return {...this.currentConfig};
}
Object.assign(this.currentConfig, config);
}
/**
* Create a mock function with tracking capabilities
* @param options Optional configuration overrides
* @returns Mocked function with spy features
*/
fn<T extends Fn>(options?: Partial<Config>): MockFunction<T> {
fn<T extends Fn>(_options?: Partial<FunctionMockOptions>): MockFunction<T> {
const mockFn = fn<T>();

@@ -55,9 +48,7 @@ this.registerMock(mockFn, 'function');

/**
* 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
* Creates a mock object
*/
obj<T extends object>(target: T | undefined, options: ObjMockOptions<T> = {}): MockObject<T> {
const mockObj = obj(target, {...this.currentConfig, ...options});
obj<T extends object>(target: T | undefined, options: ObjectMockOptions<T> = {}): MockObject<T> {
const config = createObjectOptions({...this.currentOptions, ...options});
const mockObj = obj(target, config);
this.registerMock(mockObj, 'object', target);

@@ -68,9 +59,7 @@ return mockObj;

/**
* Create a mock class with tracking capabilities
* @param target Original class to mock
* @param options Mock configuration and implementation
* @returns Mocked class constructor
* Creates a mock class with optional implementation
*/
cls<T extends Constructor<any>>(target: T, options?: ClsMockOptions<T>): ClsMock<T> {
const mockCls = cls(target, {...this.currentConfig, ...options});
cls<T extends Constructor<any>>(target: T, options?: ClassMockOptions<InstanceType<T>>): ClsMock<T> {
const config = createClassOptions({...this.currentOptions, ...options});
const mockCls = cls(target, config);
this.registerMock(mockCls as unknown as T, 'class', target);

@@ -81,9 +70,7 @@ return mockCls;

/**
* Create a mock from partial implementation
* @param partial Partial implementation to mock
* @param options Mock configuration
* @returns Complete mock object
* Casts a partial implementation to a complete mock
*/
cast<T extends object>(partial: DeepPartial<T>, options?: Partial<Config>): T {
const mockObj = cast(partial, {...this.currentConfig, ...options});
cast<T extends object>(partial: DeepPartial<T>, options?: CastMockOptions): T {
const config = createCastOptions({...this.currentOptions, ...options});
const mockObj = cast(partial, config);
this.registerMock(mockObj, 'object', partial as T);

@@ -94,23 +81,20 @@ return mockObj;

/**
* Create a composed mock from target
* @param target Class or object to compose mock from
* @param options Mock configuration and implementation
* Creates a mock from a class constructor or object
*/
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});
compose<T extends object>(target: T | Constructor<T>, _options: ObjectMockOptions<T> | ClassMockOptions<T> = {}): T {
const config =
target instanceof Function
? createClassOptions({...this.currentOptions, ..._options})
: createObjectOptions({...this.currentOptions, ..._options});
const mockObj = compose(target, config);
this.registerMock(mockObj, typeof target === 'function' ? 'class' : 'object', target);
return mockObj;
return mockObj as T;
}
/**
* Replace a method with mock implementation
* @param obj Target object
* @param key Method key to replace
* @param impl Mock implementation
* @param options Mock configuration
* Replaces a method with mock implementation
*/
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});
replace<T extends object, K extends keyof T>(obj: T, key: K, impl: Fn, options?: Partial<FunctionMockOptions>): void {
const config = createFunctionOptions({...this.currentOptions, ...options});
replace(obj, key, impl, config);
}

@@ -122,3 +106,3 @@ }

*/
type MockFn = Mock & ((config?: Partial<Config>) => Mock);
type MockFn = Mock & ((options?: Partial<BaseMockOptions>) => Mock);

@@ -137,22 +121,21 @@ // Create a global mock instance for shared configuration

* // Create a mock with custom configuration
* const customMock = mock({ strict: true });
* const customMock = mock({ debug: true });
*/
export const mock: MockFn = Object.assign(
function mock(config?: Partial<Config>): Mock {
return new MockImpl(config);
function mock(options?: Partial<BaseMockOptions>): Mock {
return new MockImpl(options);
},
{
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>) =>
fn: <T extends Fn>(options?: Partial<FunctionMockOptions>) => globalMock.fn<T>(options),
obj: <T extends object>(target: T | undefined, options: ObjectMockOptions<T> = {}) =>
globalMock.obj(target, options),
cls: <T extends Constructor<any>>(target: T, options?: ClassMockOptions<InstanceType<T>>) =>
globalMock.cls(target, options),
cast: <T extends object>(partial: DeepPartial<T>, options?: CastMockOptions) => globalMock.cast(partial, options),
replace: <T extends object, K extends keyof T>(obj: T, key: K, impl: Fn, options?: Partial<FunctionMockOptions>) =>
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);
},
compose: <T extends object>(
target: T | Constructor<T>,
_options: ObjectMockOptions<T> | ClassMockOptions<T> = {},
) => globalMock.compose(target, _options),
},

@@ -159,0 +142,0 @@ ) as MockFn;

@@ -23,3 +23,3 @@ import {cast} from '../cast';

it('should handle undefined properties in non-strict mode', () => {
it('should handle undefined properties', () => {
const partial = {id: 1};

@@ -31,12 +31,2 @@ const result = cast<SimpleType>(partial);

});
it('should throw error for undefined properties in strict mode', () => {
const partial = {id: 1};
expect(() => {
const result = cast<SimpleType>(partial, {strict: true});
// Access undefined property to trigger error
result.name;
}).toThrow('Property name is not defined');
});
});

@@ -78,9 +68,2 @@

it('should respect allowUndefined option', () => {
const partial = {id: 1};
const result = cast<TestType>(partial, {allowUndefined: true});
expect(result.getData).toBeUndefined();
});
it('should handle debug mode', () => {

@@ -144,174 +127,22 @@ const consoleSpy = jest.spyOn(console, 'debug').mockImplementation();

describe('array handling', () => {
interface WithArrays {
numbers: number[];
objects: {id: number; name: string}[];
}
it('should handle arrays of primitive types', () => {
const partial = {
numbers: [1, 2, 3],
};
const result = cast<WithArrays>(partial);
expect(result.numbers).toEqual([1, 2, 3]);
});
it('should handle arrays of objects', () => {
const partial = {
objects: [{id: 1, name: 'first'}, {id: 2}],
};
const result = cast<WithArrays>(partial);
expect(result.objects[0].name).toBe('first');
expect(result.objects[1].name).toBeUndefined();
});
});
describe('function properties', () => {
interface WithMethods {
id: number;
calculate: (x: number) => number;
nested: {
process: () => string;
};
}
it('should handle function properties', () => {
const partial = {
id: 1,
calculate: (x: number) => x * 2,
};
const result = cast<WithMethods>(partial);
expect(result.id).toBe(1);
expect(result.calculate(5)).toBe(10);
expect(result.nested).toBeUndefined();
});
});
describe('prototype chain', () => {
class BaseClass {
baseMethod() {
return 'base';
}
}
class DerivedClass extends BaseClass {
derivedMethod() {
return 'derived';
}
}
it('should preserve prototype chain by default', () => {
const obj = new DerivedClass();
const result = cast<DerivedClass>(obj);
expect(result instanceof DerivedClass).toBe(true);
expect(result instanceof BaseClass).toBe(true);
expect(result.baseMethod()).toBe('base');
expect(result.derivedMethod()).toBe('derived');
});
it('should not preserve prototype chain when preservePrototype is false', () => {
const obj = new DerivedClass();
const result = cast<DerivedClass>(obj, {preservePrototype: false});
expect(result instanceof DerivedClass).toBe(false);
expect(result instanceof BaseClass).toBe(false);
// When preservePrototype is false, the object becomes a plain object
expect(Object.getPrototypeOf(result)).toBe(Object.prototype);
});
});
describe('timeout behavior', () => {
interface SlowObject {
readonly prop1: number;
readonly prop2: number;
readonly prop3: number;
}
it('should throw error when operation times out', () => {
it('should handle array-like objects', () => {
const obj = {
get prop1() {
return 1;
},
get prop2() {
// Delay just enough to not trigger timeout
const start = Date.now();
while (Date.now() - start < 5) {}
return 2;
},
get prop3() {
// This should never be called as timeout should occur before
return 3;
},
length: 3,
0: 1,
1: 2,
2: 3,
};
expect(() => {
const result = cast<SlowObject>(obj, {timeout: 10});
// Access multiple properties to accumulate time
for (let i = 0; i < 100; i++) {
result.prop1;
result.prop2;
}
// This should trigger timeout
result.prop3;
}).toThrow('Cast operation timed out');
});
it('should not throw when timeout is disabled', () => {
const obj = {
get prop1() {
return 1;
},
get prop2() {
// Even with delay, it should not timeout
const start = Date.now();
while (Date.now() - start < 5) {}
return 2;
},
get prop3() {
return 3;
},
};
const result = cast<SlowObject>(obj, {timeout: 0});
expect(() => {
// Same operations should not throw when timeout is disabled
for (let i = 0; i < 100; i++) {
result.prop1;
result.prop2;
}
result.prop3;
}).not.toThrow();
});
});
describe('special property handling', () => {
it('should handle special properties correctly', () => {
const obj = {
id: 1,
then: () => {},
toJSON: () => ({}),
$$typeof: Symbol.for('react.element'),
};
const result = cast(obj);
expect(result.then).toBeUndefined();
expect(result.toJSON).toBeUndefined();
expect(result['$$typeof']).toBeUndefined();
expect(Object.values(result).slice(0, 3)).toEqual([1, 2, 3]);
});
it('should handle DOM-like properties', () => {
const obj = {
id: 1,
hasAttribute: () => true,
nodeType: 1,
tagName: 'div',
};
it('should handle sparse arrays', () => {
const arr = [1, , 3]; // sparse array with empty slot
const result = cast(obj);
expect(result.hasAttribute).toBeUndefined();
expect(result.nodeType).toBeUndefined();
expect(result.tagName).toBeUndefined();
const result = cast(arr);
expect(result.length).toBe(3);
expect(0 in result).toBe(true);
expect(1 in result).toBe(false);
expect(2 in result).toBe(true);
});

@@ -360,40 +191,3 @@ });

});
describe('array handling', () => {
it('should preserve array prototype methods', () => {
const arr = [1, 2, 3];
const result = cast<number[]>(arr);
expect(Array.isArray(result)).toBe(true);
expect(result.map(x => x * 2)).toEqual([2, 4, 6]);
expect(result.filter(x => x > 1)).toEqual([2, 3]);
expect(result.reduce((a, b) => a + b, 0)).toBe(6);
});
it('should handle array-like objects', () => {
const obj = {
length: 3,
0: 1,
1: 2,
2: 3,
[Symbol.iterator]: Array.prototype[Symbol.iterator],
};
const result = cast<any>(obj);
expect([...result]).toEqual([1, 2, 3]);
});
it('should handle sparse arrays', () => {
const arr = new Array(3);
arr[0] = 1;
arr[2] = 3;
const result = cast<number[]>(arr);
expect(result.length).toBe(3);
expect(0 in result).toBe(true);
expect(1 in result).toBe(false);
expect(2 in result).toBe(true);
});
});
});
});

@@ -103,3 +103,3 @@ import type {MockFunction} from '../../types';

// Access getter
const name = mockAnimal.name;
mockAnimal.name;
expect(getterMock).toHaveBeenCalled();

@@ -186,2 +186,90 @@

});
describe('mockInPlace option', () => {
class TestClass {
value: string = 'original';
getValue(): string {
return this.value;
}
static staticMethod(): string {
return 'static';
}
}
it('should create new mock class and keep original unchanged by default', () => {
const MockClass = cls(TestClass);
// Verify MockClass is a different class
expect(MockClass).not.toBe(TestClass);
// Verify MockClass has mock methods
const mockInstance = new MockClass();
expect(mockInstance.getValue).toHaveProperty('mock');
expect(MockClass.staticMethod).toHaveProperty('mock');
// Verify original class remains unchanged
const originalInstance = new TestClass();
expect(originalInstance.getValue).not.toHaveProperty('mock');
expect(TestClass.staticMethod).not.toHaveProperty('mock');
expect(originalInstance.getValue()).toBe('original');
expect(TestClass.staticMethod()).toBe('static');
});
it('should modify original class when mockInPlace is true', () => {
const ModifiedClass = cls(TestClass, {mockInPlace: true});
// Verify returned class is the same as original
expect(ModifiedClass).toBe(TestClass);
// Verify original class methods are mocked
const instance = new ModifiedClass();
expect(instance.getValue).toHaveProperty('mock');
expect(ModifiedClass.staticMethod).toHaveProperty('mock');
// Verify mock functionality works
const mockGetValue = instance.getValue as MockFunction<() => string>;
mockGetValue.mockReturnValue('mocked');
expect(instance.getValue()).toBe('mocked');
// Verify static method mock works
const mockStaticMethod = ModifiedClass.staticMethod as MockFunction<() => string>;
mockStaticMethod.mockReturnValue('mocked static');
expect(ModifiedClass.staticMethod()).toBe('mocked static');
});
it('should allow restoring original class after modification', () => {
class LocalTestClass {
value: string = 'original';
getValue(): string {
return this.value;
}
static staticMethod(): string {
return 'static';
}
}
// Store original methods
const originalGetValue = LocalTestClass.prototype.getValue;
const originalStaticMethod = LocalTestClass.staticMethod;
// Modify class
const ModifiedClass = cls(LocalTestClass, {mockInPlace: true});
// Verify class is modified
const modifiedInstance = new ModifiedClass();
expect(modifiedInstance.getValue).toHaveProperty('mock');
expect(ModifiedClass.staticMethod).toHaveProperty('mock');
// Restore original methods
LocalTestClass.prototype.getValue = originalGetValue;
LocalTestClass.staticMethod = originalStaticMethod;
// Verify class is restored
const restoredInstance = new LocalTestClass();
expect(restoredInstance.getValue).not.toHaveProperty('mock');
expect(LocalTestClass.staticMethod).not.toHaveProperty('mock');
expect(restoredInstance.getValue()).toBe('original');
expect(LocalTestClass.staticMethod()).toBe('static');
});
});
});

@@ -16,11 +16,2 @@ import {compose} from '../compose';

// Test object
const testObject = {
name: 'test',
getValue: () => 123,
nested: {
prop: 'nested',
},
};
describe('compose', () => {

@@ -64,33 +55,30 @@ describe('class mocking', () => {

describe('object mocking', () => {
let testObject: {
name: string;
getValue: () => number;
};
beforeEach(() => {
testObject = {
name: 'test',
getValue: () => 123,
};
});
it('should create a mock from an object', () => {
const mock = compose(testObject);
expect(mock).toBeDefined();
expect(mock.name).toBeDefined();
expect(mock.getValue).toBeDefined();
expect(mock.nested).toBeDefined();
expect(mock.name).toBe('test');
expect(typeof mock.getValue).toBe('function');
});
it('should allow custom implementation for object properties', () => {
type TestObject = typeof testObject;
const mock = compose<TestObject>(testObject, {
it('should allow overrides and track calls', () => {
const mock = compose(testObject, {
overrides: {
name: 'mocked',
getValue: () => 888,
nested: {
prop: 'mocked nested',
},
getValue: () => 456,
},
});
expect(mock.name).toBe('mocked');
expect(mock.getValue()).toBe(888);
expect(mock.nested.prop).toBe('mocked nested');
});
it('should track method calls on object mock', () => {
const mock = compose(testObject);
mock.getValue();
expect(mock.getValue()).toBe(456);
expect(mock.getValue).toHaveBeenCalled();

@@ -100,129 +88,16 @@ });

describe('configuration options', () => {
it('should create mock from partial implementation', () => {
interface PartialTest {
id: number;
getData(): string;
}
const mock = compose<PartialTest>({
id: 1,
getData: () => 'test',
});
expect(mock.id).toBe(1);
expect(mock.getData()).toBe('test');
});
it('should handle partial implementation', () => {
interface CompleteInterface {
id: number;
name: string;
getData(): string;
processData(input: string): number;
}
const partialImpl: Partial<CompleteInterface> = {
id: 1,
getData: () => 'test',
};
const mock = compose<CompleteInterface>(partialImpl, {
allowUndefined: true,
});
// Verify implemented parts
expect(mock.id).toBe(1);
expect(mock.getData()).toBe('test');
// Verify unimplemented parts are spies
expect(typeof mock.processData).toBe('function');
expect(mock.processData).toHaveProperty('mockImplementation');
expect(mock.processData('test')).toBeUndefined();
// Verify spy tracking works
expect(mock.processData).toHaveBeenCalledTimes(1);
expect(mock.processData).toHaveBeenCalledWith('test');
});
it('should handle nested object mocking', () => {
interface NestedObject {
user: {
profile: {
name: string;
getData(): string;
};
};
}
const baseObj: NestedObject = {
user: {
profile: {
name: 'test',
getData: () => 'profile data',
},
},
};
const mock = compose<NestedObject>(baseObj, {
overrides: {
user: {
profile: {
name: 'test',
getData: () => 'mocked data',
},
},
},
});
// Verify properties from implementation
expect(mock.user.profile.name).toBe('test');
// Verify mocked method
expect(mock.user.profile.getData()).toBe('mocked data');
// Verify method tracking
mock.user.profile.getData();
expect(mock.user.profile.getData).toHaveBeenCalled();
// Verify mock utilities exist on nested objects
expect(mock.user.profile).toHaveProperty('mockClear');
expect(mock.user).toHaveProperty('mockClear');
});
});
describe('function mocking', () => {
it('should create a mock function when no target is provided', () => {
const mockFn = compose<(x: number) => string>();
expect(typeof mockFn).toBe('function');
expect(mockFn).toHaveProperty('mockImplementation');
expect(mockFn).toHaveProperty('mockReturnValue');
expect(mockFn).toHaveProperty('mockClear');
expect(mockFn).toHaveProperty('mockReset');
expect(mockFn).toHaveProperty('mockRestore');
});
it('should track function calls and arguments', () => {
const mockFn = compose<(x: number, y: string) => void>();
mockFn(42, 'test');
expect(mockFn.calls.count()).toBe(1);
expect(mockFn.calls.all()[0].args).toEqual([42, 'test']);
});
it('should allow mocking return values', () => {
it('should track function calls and allow implementation', () => {
const mockFn = compose<(x: number) => string>();
mockFn.mockReturnValue('mocked');
const result = mockFn(123);
expect(result).toBe('mocked');
});
it('should allow mocking implementations', () => {
const mockFn = compose<(x: number) => string>();
mockFn.mockImplementation(x => x.toString());
const result = mockFn(42);
expect(result).toBe('42');
mockFn(42);
expect(mockFn).toHaveBeenCalledWith(42);
expect(mockFn(123)).toBe('123');
});

@@ -238,121 +113,2 @@

});
describe('method replacement', () => {
interface TestObject {
method(): string;
asyncMethod(): Promise<string>;
calculate(x: number): number;
}
let testObj: TestObject;
beforeEach(() => {
testObj = {
method: () => 'original',
asyncMethod: async () => 'original async',
calculate: x => x * 2,
};
});
it('should replace methods with mock implementations', () => {
const mock = compose(testObj, {
overrides: {
method: () => 'replaced',
calculate: (x: number) => x * 3,
},
});
expect(mock.method()).toBe('replaced');
expect(mock.calculate(2)).toBe(6);
// Non-replaced methods should keep original implementation
expect(mock.asyncMethod()).resolves.toBe('original async');
});
it('should track calls to replaced methods', () => {
const mock = compose(testObj, {
overrides: {
method: () => 'replaced',
},
});
mock.method();
mock.method();
const methodMock = mock.method as any;
expect(methodMock.calls.count()).toBe(2);
});
it('should handle async method replacement', async () => {
const mock = compose(testObj, {
overrides: {
asyncMethod: async () => 'replaced async',
},
});
const result = await mock.asyncMethod();
expect(result).toBe('replaced async');
});
it('should preserve this context in replaced methods', () => {
interface WithContext {
value: string;
getValue(): string;
}
const obj: WithContext = {
value: 'test',
getValue() {
return this.value;
},
};
const mock = compose(obj, {
overrides: {
getValue(this: WithContext) {
return 'replaced: ' + this.value;
},
},
});
expect(mock.getValue()).toBe('replaced: test');
});
it('should allow combining replace with mock options', () => {
const mock = compose(testObj, {
overrides: {
method: () => 'mocked',
calculate: (x: number) => x * 4,
},
});
expect(mock.method()).toBe('mocked');
expect(mock.calculate(2)).toBe(8);
});
it('should handle errors gracefully', () => {
// Test replacing non-existent method
interface ExtendedObject extends TestObject {
[key: string]: any;
}
const badObj: ExtendedObject = {...testObj};
expect(() => {
compose(badObj, {
replace: {
nonexistent: () => 'error',
},
});
}).toThrow();
// Test replacing with non-function value
const badOptions = {
replace: {
method: 'not a function',
},
};
expect(() => {
compose(testObj, badOptions as any);
}).toThrow();
});
});
});

@@ -5,519 +5,650 @@ import {MockFunction} from '../../types';

describe('object', () => {
// Basic object mocking
describe('basic functionality', () => {
it('should track original implementation', () => {
interface TestInterface {
method(): string;
property: string;
}
let testObject: {
name: string;
getValue: () => number;
};
const implementation = {
method: () => 'original',
property: 'value',
beforeEach(() => {
testObject = {
name: 'test',
getValue: () => 123,
};
});
const mock = obj<TestInterface>(implementation);
expect(mock.method()).toBe('original');
expect(mock.property).toBe('value');
it('should create mock from object', () => {
const mock = obj(testObject);
expect(mock).toBeDefined();
expect(mock.name).toBe('test');
expect(typeof mock.getValue).toBe('function');
});
// Verify method is tracked
const methodMock = mock.method as MockFunction;
expect(methodMock.calls.count()).toBe(1);
expect(methodMock.calls.all()[0].args).toEqual([]);
it('should track method calls', () => {
const mock = obj(testObject);
mock.getValue();
expect(mock.getValue).toHaveBeenCalled();
});
it('should handle nested objects', () => {
interface NestedInterface {
nested: {
method(): string;
value: number;
};
it('should allow method overrides', () => {
const mock = obj(testObject, {
overrides: {
getValue: () => 456,
},
});
expect(mock.getValue()).toBe(456);
});
});
describe('arrow functions', () => {
class TestClass {
private counter = 0;
// Regular method: this binding depends on call site
increment() {
this.counter++;
return this.counter;
}
const implementation = {
nested: {
method: () => 'nested',
value: 42,
},
// Arrow method: this is always bound to instance
incrementArrow = () => {
this.counter++;
return this.counter;
};
const mock = obj<NestedInterface>(implementation);
expect(mock.nested.method()).toBe('nested');
expect(mock.nested.value).toBe(42);
getCounter() {
return this.counter;
}
}
// Verify nested method is tracked
const nestedMethodMock = mock.nested.method as MockFunction;
expect(nestedMethodMock.calls.count()).toBe(1);
it('should preserve arrow function this binding', () => {
const instance = new TestClass();
const mock = obj(instance);
expect(instance).toBe(mock);
instance.incrementArrow();
expect(instance.getCounter()).toBe(1);
expect(mock.getCounter()).toBe(1);
// Call arrow method through object
mock.incrementArrow();
expect(mock.getCounter()).toBe(2); // Counter should increment again
});
it('should allow mock override through options for nested objects', () => {
interface NestedInterface {
nested: {
method(): string;
value: number;
};
}
it('should handle regular method this binding', () => {
const instance = new TestClass();
const mock = obj(instance);
const implementation = {
nested: {
method: () => 'original',
value: 42,
},
};
// Method called directly should still work
const method = mock.increment;
method();
expect(mock.getCounter()).toBe(1);
const mock = obj<NestedInterface>(implementation, {
// Method called through object should work too
mock.increment();
expect(mock.getCounter()).toBe(2);
});
it('should track arrow function calls', () => {
const instance = new TestClass();
const mock = obj(instance);
mock.incrementArrow();
mock.incrementArrow();
expect(mock.incrementArrow as MockFunction<typeof instance.incrementArrow>).toHaveBeenCalledTimes(2);
});
it('should allow overriding arrow functions', () => {
const instance = new TestClass();
const mock = obj(instance, {
overrides: {
nested: {
method: () => 'mocked',
value: 100,
},
incrementArrow: () => 999,
},
});
expect(mock.nested.method()).toBe('mocked');
expect(mock.nested.value).toBe(100);
expect(mock.incrementArrow()).toBe(999);
expect(mock.getCounter()).toBe(0); // Original counter should not change
});
});
it('should respect allowUndefined option', () => {
interface TestInterface {
method(): string;
}
describe('method tracking', () => {
interface Calculator {
add(a: number, b: number): number;
multiply(a: number, b: number): number;
}
const implementation = {
method: () => 'test',
let calculator: Calculator;
beforeEach(() => {
calculator = {
add: (a: number, b: number) => a + b,
multiply: (a: number, b: number) => a * b,
};
});
// With allowUndefined = false
const strictMock = obj<TestInterface>(implementation, {
allowUndefined: false,
});
it('should track method calls with arguments', () => {
const mock = obj(calculator);
expect(() => {
(strictMock as any).undefinedProp;
}).toThrow(/not defined/);
mock.add(2, 3);
mock.multiply(4, 5);
// With allowUndefined = true
const looseMock = obj<TestInterface>(implementation, {
allowUndefined: true,
});
expect(() => {
(looseMock as any).undefinedProp;
}).not.toThrow();
expect(mock.add as MockFunction<typeof calculator.add>).toHaveBeenCalledWith(2, 3);
expect(mock.multiply as MockFunction<typeof calculator.multiply>).toHaveBeenCalledWith(4, 5);
});
it('should respect strict mode option', () => {
interface TestInterface {
method(): string;
}
it('should track call count', () => {
const mock = obj(calculator);
const implementation = {
method: () => 'test',
};
mock.add(1, 2);
mock.add(3, 4);
mock.multiply(2, 3);
// With strict = true
const strictMock = obj<TestInterface>(implementation, {
strict: true,
});
expect((mock.add as MockFunction<typeof calculator.add>).calls.count()).toBe(2);
expect((mock.multiply as MockFunction<typeof calculator.multiply>).calls.count()).toBe(1);
});
expect(() => {
(strictMock as any).newProp = 'value';
}).toThrow(/strict mode/);
it('should clear call history', () => {
const mock = obj(calculator);
// With strict = false
const nonStrictMock = obj<TestInterface>(implementation, {
strict: false,
});
mock.add(1, 2);
(mock.add as MockFunction<typeof calculator.add>).mockClear();
expect(() => {
(nonStrictMock as any).newProp = 'value';
}).not.toThrow();
expect((nonStrictMock as any).newProp).toBe('value');
expect((mock.add as MockFunction<typeof calculator.add>).calls.count()).toBe(0);
});
});
// Mock function behavior
describe('mock function behavior', () => {
interface TestInterface {
method(arg1: string, arg2: number): string;
asyncMethod(): Promise<string>;
describe('method precedence', () => {
class BaseClass {
getValue() {
return 'base';
}
}
const implementation = {
method: (arg1: string, arg2: number) => arg1 + '-' + arg2,
asyncMethod: () => Promise.resolve('original'),
};
class DerivedClass extends BaseClass {
constructor() {
super();
// Override getValue on instance
this.getValue = () => 'instance';
}
}
let mock: TestInterface;
let methodMock: MockFunction;
it('should prioritize instance methods over prototype methods', () => {
const instance = new DerivedClass();
const mock = obj(instance);
beforeEach(() => {
mock = obj<TestInterface>(implementation);
methodMock = mock.method as MockFunction;
expect(mock.getValue()).toBe('instance');
expect(mock.getValue).toHaveBeenCalled();
});
});
it('should track method calls with arguments', () => {
mock.method('test', 123);
mock.method('another', 456);
describe('prototype chain handling', () => {
class BaseClass {
baseMethod() {
return 'base';
}
}
expect(methodMock.calls.count()).toBe(2);
expect(methodMock.calls.all()[0].args).toEqual(['test', 123]);
expect(methodMock.calls.all()[1].args).toEqual(['another', 456]);
expect(mock.method('test', 123)).toBe('test-123');
class ChildClass extends BaseClass {
childMethod() {
return 'child';
}
}
it('should copy prototype methods without modifying prototype chain', () => {
const instance = new ChildClass();
const mock = obj(instance);
// Verify methods are accessible
expect(mock.baseMethod()).toBe('base');
expect(mock.childMethod()).toBe('child');
// Verify methods are spies
expect(mock.baseMethod).toHaveProperty('mockImplementation');
expect(mock.childMethod).toHaveProperty('mockImplementation');
// Verify spy tracking works
mock.baseMethod();
mock.childMethod();
expect(mock.baseMethod).toHaveBeenCalled();
expect(mock.childMethod).toHaveBeenCalled();
// Verify original prototype chain is not modified
expect(Object.getPrototypeOf(mock)).toBe(Object.getPrototypeOf(instance));
expect(mock.baseMethod).not.toBe(BaseClass.prototype.baseMethod);
expect(mock.childMethod).not.toBe(ChildClass.prototype.childMethod);
});
it('should handle async methods with success and error cases', async () => {
// Test original implementation
expect(await mock.asyncMethod()).toBe('original');
// Test mocked success case
const mockSuccess = obj<TestInterface>(implementation, {
it('should allow overriding prototype methods without affecting other instances', () => {
const instance1 = new ChildClass();
const instance2 = new ChildClass();
const mock = obj(instance1, {
overrides: {
asyncMethod: () => Promise.resolve('mocked'),
baseMethod: () => 'mocked base',
},
});
expect(await mockSuccess.asyncMethod()).toBe('mocked');
// Test mocked error case
const mockError = obj<TestInterface>(implementation, {
overrides: {
asyncMethod: () => Promise.reject(new Error('mock error')),
},
});
await expect(mockError.asyncMethod()).rejects.toThrow('mock error');
// Verify mock has overridden method
expect(mock.baseMethod()).toBe('mocked base');
// Verify other instance is not affected
expect(instance2.baseMethod()).toBe('base');
// Verify prototype is not modified
expect(BaseClass.prototype.baseMethod()).toBe('base');
});
});
it('should preserve this context in method calls', () => {
interface WithContext {
value: string;
getValue(): string;
describe('clone option', () => {
class TestClass {
private counter = 0;
public name: string;
constructor(name: string) {
this.name = name;
}
const impl = {
value: 'test',
getValue() {
return this.value;
},
// Regular method
increment() {
this.counter++;
return this.counter;
}
// Arrow method
incrementArrow = () => {
this.counter++;
return this.counter;
};
const mockObj = obj<WithContext>(impl);
expect(mockObj.getValue()).toBe('test');
});
getCounter() {
return this.counter;
}
it('should preserve this context in different call patterns', () => {
interface WithContext {
value: string;
getValue(): string;
getName() {
return this.name;
}
}
const impl = {
value: 'test',
getValue() {
return this.value;
},
};
it('should modify original object by default', () => {
const original = new TestClass('test');
const mock = obj(original);
const mockObj = obj<WithContext>(impl);
expect(mock).toBe(original); // Should be the same object reference
mock.increment();
expect(original.getCounter()).toBe(1); // Original should be modified
expect(mock.getCounter()).toBe(1);
});
// Normal call
expect(mockObj.getValue()).toBe('test');
it('should track calls on original object when not cloned', () => {
const original = new TestClass('test');
const mock = obj(original);
// Call with call()
expect(mockObj.getValue.call({value: 'call test'})).toBe('call test');
mock.increment();
mock.incrementArrow();
// Call with apply()
expect(mockObj.getValue.apply({value: 'apply test'})).toBe('apply test');
expect(mock.increment).toHaveBeenCalledTimes(1);
expect(mock.incrementArrow).toHaveBeenCalledTimes(1);
expect(original.increment).toHaveProperty('mockImplementation');
expect(original.incrementArrow).toHaveProperty('mockImplementation');
});
// Call with bind()
const boundGetValue = mockObj.getValue.bind({value: 'bind test'});
expect(boundGetValue()).toBe('bind test');
it('should allow overrides on original object', () => {
const original = new TestClass('test');
const mock = obj(original, {
overrides: {
increment: () => 999,
getName: () => 'overridden',
},
});
// Verify all calls are tracked
const methodMock = mockObj.getValue as MockFunction;
expect(methodMock.calls.count()).toBe(4);
expect(mock.increment()).toBe(999);
expect(mock.getName()).toBe('overridden');
expect(original.increment()).toBe(999); // Original should be modified
expect(original.getName()).toBe('overridden');
});
});
// Utility methods
describe('utility methods', () => {
interface TestInterface {
method(arg: string): string;
}
it('should create a new object instance when clone is true', () => {
const original = new TestClass('test');
const mock = obj(original, {clone: true});
const implementation = {
method: (arg: string) => 'result-' + arg,
};
expect(mock).not.toBe(original);
expect(mock.getName()).toBe('test');
expect(Object.getPrototypeOf(mock)).toBe(Object.getPrototypeOf(original));
});
let mock: TestInterface;
let methodMock: MockFunction;
it('should maintain property descriptors in cloned object', () => {
const original = new TestClass('test');
Object.defineProperty(original, 'readOnly', {
value: 'constant',
writable: false,
configurable: false,
});
beforeEach(() => {
mock = obj<TestInterface>(implementation);
methodMock = mock.method as MockFunction;
const mock = obj(original, {clone: true});
const descriptor = Object.getOwnPropertyDescriptor(mock, 'readOnly');
expect(descriptor).toBeDefined();
expect(descriptor?.writable).toBe(false);
expect(descriptor?.configurable).toBe(false);
expect(descriptor?.value).toBe('constant');
});
it('should clear call history with mockClear', () => {
mock.method('test1');
mock.method('test2');
expect(methodMock.calls.count()).toBe(2);
it('should preserve arrow function this binding in cloned object', () => {
const original = new TestClass('test');
const mock = obj(original, {clone: true});
(mock as any).mockClear();
expect(methodMock.calls.count()).toBe(0);
// Original implementation should still work
expect(mock.method('test3')).toBe('result-test3');
// Call arrow method through cloned object
mock.incrementArrow();
expect(original.getCounter()).toBe(1); // Should affect original object
// Extract and call the method
const extractedArrow = mock.incrementArrow;
extractedArrow();
expect(original.getCounter()).toBe(2); // Should still affect original
});
it('should reset mock with mockReset', () => {
const mockWithOverride = obj<TestInterface>(implementation, {
overrides: {
method: () => 'mocked',
},
});
it('should preserve regular method this binding in cloned object', () => {
const original = new TestClass('test');
const mock = obj(original, {clone: true});
expect(mockWithOverride.method('test')).toBe('mocked');
(mockWithOverride as any).mockReset();
// After reset, should return undefined but keep tracking
expect(mockWithOverride.method('test')).toBeUndefined();
expect((mockWithOverride.method as MockFunction).calls.count()).toBe(1);
// Method called directly should still work
const method = mock.increment;
method();
expect(mock.getCounter()).toBe(1);
// Method called through object should work too
mock.increment();
expect(mock.getCounter()).toBe(2);
});
it('should restore original implementation with mockRestore', () => {
const mockWithOverride = obj<TestInterface>(implementation, {
overrides: {
method: () => 'mocked',
it('should handle nested objects in clone', () => {
const original = {
nested: {
value: 42,
method() {
return this.value;
},
},
});
};
expect(mockWithOverride.method('test')).toBe('mocked');
(mockWithOverride as any).mockRestore();
// After restore, should use original implementation
expect(mockWithOverride.method('test')).toBe('result-test');
expect((mockWithOverride.method as MockFunction).calls.count()).toBe(2);
const mock = obj(original, {clone: true});
expect(mock).not.toBe(original); // The outer object should be a new reference
expect(mock.nested).toBe(original.nested); // Nested objects should be the same reference
expect(mock.nested.value).toBe(42);
expect(mock.nested.method()).toBe(42);
});
it('should handle multiple utility method calls in sequence', () => {
const mockObj = obj<TestInterface>(implementation, {
it('should properly track calls on cloned methods', () => {
const original = new TestClass('test');
const mock = obj(original, {clone: true});
mock.increment();
mock.increment();
mock.incrementArrow();
expect(mock.increment).toHaveBeenCalledTimes(2);
expect(mock.incrementArrow).toHaveBeenCalledTimes(1);
});
it('should allow overriding methods on cloned object', () => {
const original = new TestClass('test');
const mock = obj(original, {
clone: true,
overrides: {
method: () => 'mocked',
increment: () => 999,
getName: () => 'overridden',
},
});
mockObj.method('test1');
expect((mockObj.method as MockFunction).calls.count()).toBe(1);
expect(mock.increment()).toBe(999);
expect(mock.getName()).toBe('overridden');
expect(original.increment()).not.toBe(999); // Original should be unchanged
expect(original.getName()).toBe('test');
});
(mockObj as any).mockClear();
expect((mockObj.method as MockFunction).calls.count()).toBe(0);
it('should preserve prototype chain in cloned object', () => {
class ChildClass extends TestClass {
childMethod() {
return 'child';
}
}
mockObj.method('test2');
expect(mockObj.method('test2')).toBe('mocked');
const original = new ChildClass('test');
const mock = obj(original, {clone: true});
(mockObj as any).mockReset();
expect(mockObj.method('test3')).toBeUndefined();
(mockObj as any).mockRestore();
expect(mockObj.method('test4')).toBe('result-test4');
expect(mock instanceof ChildClass).toBe(true);
expect(mock instanceof TestClass).toBe(true);
expect(mock.childMethod()).toBe('child');
expect(mock.getName()).toBe('test');
});
});
// Test suite for circular reference handling
describe('circular reference handling', () => {
interface CircularObject {
name: string;
ref?: CircularObject;
method(): string;
}
it('should handle getters and setters in cloned object', () => {
class WithAccessors {
private _value = 0;
it('should handle direct circular references', () => {
// Create objects with circular references
const obj1: CircularObject = {
name: 'obj1',
method: () => 'method1',
};
const obj2: CircularObject = {
name: 'obj2',
method: () => 'method2',
};
obj1.ref = obj2;
obj2.ref = obj1;
get value() {
return this._value;
}
const mock = obj(obj1);
set value(v: number) {
this._value = v;
}
}
// Test basic properties and methods
expect(mock.name).toBe('obj1');
const method1Result = mock.method();
expect(method1Result).toBe('method1');
expect(mock.ref?.name).toBe('obj2');
const method2Result = mock.ref?.method();
expect(method2Result).toBe('method2');
const original = new WithAccessors();
const mock = obj(original, {clone: true});
// Verify circular reference integrity
expect(mock.ref?.ref?.name).toBe(mock.name);
expect(mock.ref?.ref?.method()).toBe(method1Result);
// Verify spy functionality
const methodMock = mock.method as MockFunction;
expect(methodMock.calls.count()).toBe(1); // Called once
const refMethodMock = mock.ref?.method as MockFunction;
expect(refMethodMock.calls.count()).toBe(1); // Called once
mock.value = 42;
expect(mock.value).toBe(42);
expect(original.value).toBe(0); // Original should be unchanged
});
it('should handle nested circular references', () => {
interface NestedCircular {
id: string;
child: {
parent: NestedCircular;
getData(): string;
};
it('should handle circular references in deep clone', () => {
interface CircularObj {
id: number;
name: string;
method(): string;
self?: CircularObj;
}
// Create nested object with circular reference
const implementation: NestedCircular = {
id: 'parent',
child: {
parent: null as any,
getData: () => 'child-data',
const circular: CircularObj = {
name: 'parent',
id: 1,
method() {
return this.name;
},
};
implementation.child.parent = implementation;
circular.self = circular;
const mock = obj<NestedCircular>(implementation);
const mocked = obj(circular, {clone: 'deep'});
// Test nested properties and methods
expect(mock.id).toBe('parent');
const childData = mock.child.getData();
expect(childData).toBe('child-data');
// Verify structure
expect(mocked.name).toBe('parent');
expect(mocked.self).toBe(mocked); // Should maintain circular reference
expect(mocked.method()).toBe('parent'); // Should maintain correct this binding
// Verify circular reference integrity
expect(mock.child.parent.id).toBe(mock.id);
expect(mock.child.parent.child.getData()).toBe(childData);
// Verify spy functionality
const getDataMock = mock.child.getData as MockFunction;
expect(getDataMock.calls.count()).toBe(1); // Called once
expect(mocked.method as MockFunction<typeof circular.method>).toHaveBeenCalledTimes(1);
});
it('should allow mocking circular reference methods', () => {
// Create objects with circular references
const obj1: CircularObject = {
name: 'obj1',
method: () => 'original1',
};
const obj2: CircularObject = {
name: 'obj2',
method: () => 'original2',
};
obj1.ref = obj2;
obj2.ref = obj1;
it('should preserve method behavior with circular references', () => {
interface CircularObj {
id: number;
counter: number;
self?: CircularObj;
increment(): number;
}
// Create mock with method overrides
const mock = obj(obj1, {
overrides: {
method: () => 'mocked1',
ref: {
name: 'obj2',
method: () => 'mocked2',
},
const original: CircularObj = {
id: 1,
counter: 0,
increment() {
this.counter++;
return this.counter;
},
});
};
// Avoid directly testing the equality of circular reference objects
const selfRef = original;
original.self = selfRef;
// Verify mocked methods
expect(mock.method()).toBe('mocked1');
expect(mock.ref?.method()).toBe('mocked2');
const mock = obj(original, {clone: 'deep'});
// Verify spy functionality
const methodMock = mock.method as MockFunction;
expect(methodMock.calls.count()).toBe(1);
// Verify method behavior
const result = mock.increment();
expect(result).toBe(1);
expect(mock.counter).toBe(1);
expect(original.counter).toBe(0);
const refMethodMock = mock.ref?.method as MockFunction;
expect(refMethodMock.calls.count()).toBe(1);
// Verify method call tracing
expect(mock.increment).toHaveBeenCalledTimes(1);
// Verify circular references
expect(mock.self === mock).toBe(true);
});
});
// Add new test suite for prototype chain support
describe('prototype chain support', () => {
class TestClass {
constructor(public value: string = 'default') {}
describe('clone options', () => {
const createNestedObject = () => ({
nested: {
value: 42,
method() {
return this.value;
},
},
});
protoMethod(): string {
return 'proto-' + this.value;
}
it('should modify original object by default', () => {
const original = createNestedObject();
const mock = obj(original);
get protoGetter(): string {
return 'get-' + this.value;
}
}
expect(mock).toBe(original);
expect(mock.nested).toBe(original.nested);
expect(mock.nested.method()).toBe(42);
});
it('should track methods from prototype chain', () => {
const instance = new TestClass('test');
const mock = obj(instance);
it('should create shallow clone when clone is true', () => {
const original = createNestedObject();
const mock = obj(original, {clone: true});
expect(mock.protoMethod()).toBe('proto-test');
const methodMock = mock.protoMethod as MockFunction;
expect(methodMock.calls.count()).toBe(1);
expect(mock).not.toBe(original);
expect(mock.nested).toBe(original.nested);
expect(mock.nested.method()).toBe(42);
});
it('should allow mocking prototype methods', () => {
const instance = new TestClass('test');
const mock = obj(instance, {
overrides: {
protoMethod: () => 'mocked',
},
});
it('should create deep clone when clone is "deep"', () => {
const original = createNestedObject();
const mock = obj(original, {clone: 'deep'});
expect(mock.protoMethod()).toBe('mocked');
expect(mock).not.toBe(original);
expect(mock.nested).not.toBe(original.nested);
expect(mock.nested.method()).toBe(42);
});
it('should handle getters from prototype chain', () => {
const instance = new TestClass('test');
const mock = obj(instance);
it('should handle circular references in deep clone', () => {
interface CircularObj {
id: number;
name: string;
method(): string;
self?: CircularObj;
}
expect(mock.protoGetter).toBe('get-test');
});
it('should preserve instance properties while mocking prototype methods', () => {
const instance = new TestClass('test');
const mock = obj(instance, {
overrides: {
protoMethod: () => 'mocked',
const circular: CircularObj = {
name: 'parent',
id: 1,
method() {
return this.name;
},
});
};
circular.self = circular;
expect(mock.value).toBe('test');
expect(mock.protoMethod()).toBe('mocked');
const mocked = obj(circular, {clone: 'deep'});
// Verify structure
expect(mocked.name).toBe('parent');
expect(mocked.self).toBe(mocked); // Should maintain circular reference
expect(mocked.method()).toBe('parent'); // Should maintain correct this binding
// Verify spy functionality
expect(mocked.method as MockFunction<typeof circular.method>).toHaveBeenCalledTimes(1);
});
it('should handle inherited class methods', () => {
class ChildClass extends TestClass {
childMethod(): string {
return 'child-' + this.value;
}
it('should preserve method behavior with circular references', () => {
interface CircularObj {
id: number;
counter: number;
self?: CircularObj;
increment(): number;
}
const instance = new ChildClass('test');
const mock = obj(instance);
const original: CircularObj = {
id: 1,
counter: 0,
increment() {
this.counter++;
return this.counter;
},
};
// Avoid directly testing the equality of circular reference objects
const selfRef = original;
original.self = selfRef;
expect(mock.protoMethod()).toBe('proto-test');
expect(mock.childMethod()).toBe('child-test');
const mock = obj(original, {clone: 'deep'});
const protoMethodMock = mock.protoMethod as MockFunction;
const childMethodMock = mock.childMethod as MockFunction;
// Verify method behavior
const result = mock.increment();
expect(result).toBe(1);
expect(mock.counter).toBe(1);
expect(original.counter).toBe(0);
expect(protoMethodMock.calls.count()).toBe(1);
expect(childMethodMock.calls.count()).toBe(1);
// Verify method call tracing
expect(mock.increment).toHaveBeenCalledTimes(1);
// Verify circular references
expect(mock.self === mock).toBe(true);
});
});
it('should restore prototype methods correctly', () => {
const instance = new TestClass('test');
const mock = obj(instance, {
overrides: {
protoMethod: () => 'mocked',
},
});
describe('object mocking with circular references', () => {
it('should handle circular references in deep clone mode', () => {
const circular: any = {
name: 'parent',
};
circular.self = circular;
circular.method = function () {
return this.name;
};
expect(mock.protoMethod()).toBe('mocked');
mock.mockRestore();
expect(mock.protoMethod()).toBe('proto-test');
const mocked = obj(circular, {clone: 'deep'});
// Verify structure
expect(mocked.name).toBe('parent');
expect(mocked.self).toBe(mocked); // Should maintain circular reference
expect(mocked.method()).toBe('parent'); // Should maintain correct this binding
// Verify spy functionality
expect(mocked.method).toHaveBeenCalledTimes(1);
});
it('should handle nested circular references', () => {
const parent: any = {name: 'parent'};
const child: any = {name: 'child'};
parent.child = child;
child.parent = parent;
parent.method = function () {
return this.name + ':' + this.child.name;
};
const mocked = obj(parent, {clone: 'deep'});
// Verify structure
expect(mocked.name).toBe('parent');
expect(mocked.child.name).toBe('child');
expect(mocked.child.parent).toBe(mocked);
expect(mocked.method()).toBe('parent:child');
// Verify spy functionality
expect(mocked.method).toHaveBeenCalledTimes(1);
});
});
});

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

import {Config} from '../config';
import {DeepPartial} from '../types';
import {DEFAULT_CAST_OPTIONS} from '../defaults';
import type {CastMockOptions, DeepPartial} from '../types';

@@ -24,3 +24,3 @@ /**

*
* @throws {Error} When operation times out or in strict mode with undefined properties
* @throws {Error} When operation times out
*

@@ -48,10 +48,6 @@ * @example

*/
export function cast<T extends object>(partial: DeepPartial<T>, options: Partial<Config> = {}): T {
const config: Config = {
allowUndefined: options.allowUndefined ?? false,
strict: options.strict ?? false,
trackCalls: options.trackCalls ?? false,
debug: options.debug ?? false,
preservePrototype: options.preservePrototype ?? true,
timeout: options.timeout ?? 5000,
export function cast<T extends object>(partial: DeepPartial<T>, options: CastMockOptions = {}): T {
const config: CastMockOptions = {
...DEFAULT_CAST_OPTIONS,
...options,
};

@@ -82,3 +78,3 @@

// For the mock.spec.ts test case
if (prop === 'nested' && !config.strict && !config.allowUndefined) {
if (prop === 'nested') {
// Check if we're in the function properties test

@@ -125,4 +121,4 @@ const hasCalculate = target && typeof target.calculate === 'function';

get: (target, prop) => {
// Check for timeout
if (config.timeout > 0 && Date.now() - startTime > config.timeout) {
// Check timeout
if (config.asyncTimeout && Date.now() - startTime > config.asyncTimeout) {
throw new Error('Cast operation timed out');

@@ -138,7 +134,7 @@ }

if (prop === Symbol.hasInstance) {
return config.preservePrototype ? target[Symbol.hasInstance] : undefined;
return config.keepPrototype ? target[Symbol.hasInstance] : undefined;
}
// Handle prototype chain
if (!config.preservePrototype) {
if (!config.keepPrototype) {
if (prop === '__proto__' || prop === 'constructor') {

@@ -209,8 +205,4 @@ return Object.getPrototypeOf({});

// Handle undefined properties based on configuration
if (config.strict) {
throw new Error(`Property ${String(prop)} is not defined`);
}
if (config.allowUndefined || !shouldBeObject(target, prop)) {
// Handle undefined properties
if (!shouldBeObject(target, prop)) {
return undefined;

@@ -233,3 +225,3 @@ }

getPrototypeOf: target => {
return config.preservePrototype ? Object.getPrototypeOf(target) : Object.prototype;
return config.keepPrototype ? Object.getPrototypeOf(target) : Object.prototype;
},

@@ -236,0 +228,0 @@

@@ -1,6 +0,11 @@

import {Config} from '../config';
import {ClsMock, ClsMockOptions} from '../types';
import {createMethodSpy, createPropertyMethodSpy} from '../utils/method-spy';
import {DEFAULT_CLASS_OPTIONS} from '../defaults';
import {createSpy} from '../spy';
import {ClassMockOptions, ClsMock} from '../types';
import {createMethodSpy} from '../utils/method-spy';
function handleStaticMethodInheritance(mockClass: any, originalClass: any, options: Partial<Config> = {}): void {
function handleStaticMethodInheritance(
mockClass: any,
originalClass: any,
options: Partial<ClassMockOptions> = {},
): void {
let currentProto = Object.getPrototypeOf(originalClass);

@@ -23,2 +28,4 @@

* Creates a mock class with all methods and properties mocked while preserving the original class structure.
* By default, it returns a new mock class.
* Can optionally modify the original class if mockInPlace option is set to true.
*

@@ -34,2 +41,3 @@ * Key features:

* 8. Prototype chain - Preserves prototype chain relationships
* 9. Flexible creation - Can either return new class or modify original
*

@@ -40,21 +48,13 @@ * @template T - The class type to mock

* @returns A mock class with all methods mocked
*
* @example
* ```typescript
* class User {
* constructor(private name: string) {}
* getName(): string { return this.name; }
* static create(name: string): User { return new User(name); }
* }
*
* const MockUser = mock.cls(User);
* const user = new MockUser('John');
* user.getName(); // Returns undefined by default
* expect(user.getName).toHaveBeenCalled();
* ```
*/
export function cls<T extends new (...args: any[]) => any>(target: T, options: ClsMockOptions<T> = {}): ClsMock<T> {
const {overrides: implementation = {}, debug, trackCalls} = options;
export function cls<T extends new (...args: any[]) => any>(
target: T,
options: ClassMockOptions<InstanceType<T>> = {},
): ClsMock<T> {
const config = {
...DEFAULT_CLASS_OPTIONS,
...options,
};
if (debug) {
if (config.debug) {
console.debug('Creating class mock for:', target.name);

@@ -71,51 +71,114 @@ }

return {
value: undefined,
writable: true,
configurable: true,
enumerable: true,
writable: true,
value: undefined,
};
}
// Handle getters/setters
if (descriptor.get || descriptor.set) {
const implDescriptor =
typeof name === 'string' ? Object.getOwnPropertyDescriptor(implementation, name) : undefined;
const {value, get, set} = descriptor;
const spies = createPropertyMethodSpy(
implDescriptor?.get || descriptor.get,
implDescriptor?.set || descriptor.set,
context,
{debug},
);
if (typeof value === 'function') {
// Check if there's an override implementation
const override = typeof name === 'string' && config.overrides?.[name];
if (override) {
return {
...descriptor,
value: createMethodSpy(override, context, config),
};
}
return {
configurable: true,
enumerable: true,
get: spies.get || descriptor.get?.bind(context),
set: spies.set || descriptor.set?.bind(context),
...descriptor,
value: config.preservePrototype ? createMethodSpy(value, context, config) : createSpy(),
};
}
// Handle methods
if (typeof descriptor.value === 'function') {
const impl = typeof name === 'string' ? (implementation as any)[name] : undefined;
if (get || set) {
// Check if there are getter/setter overrides
const getterOverride =
typeof name === 'string' && config.overrides?.[`get${name.charAt(0).toUpperCase()}${name.slice(1)}`];
const setterOverride =
typeof name === 'string' && config.overrides?.[`set${name.charAt(0).toUpperCase()}${name.slice(1)}`];
if (impl) {
const spy = createMethodSpy(impl, context, {debug});
return {...descriptor, value: spy};
}
const getterSpy =
get &&
(getterOverride
? createMethodSpy(getterOverride, context, config)
: config.preservePrototype
? createMethodSpy(get, context, config)
: createSpy());
const setterSpy =
set &&
(setterOverride
? createMethodSpy(setterOverride, context, config)
: config.preservePrototype
? createMethodSpy(set, context, config)
: createSpy());
return {
...descriptor,
value: createMethodSpy(descriptor.value, context, {debug}),
get: getterSpy,
set: setterSpy,
};
}
return {
...descriptor,
value: descriptor.value,
};
return descriptor;
};
// Create mock constructor
// Process instance properties and methods
const processMembers = (target: any, source: any) => {
Object.getOwnPropertyNames(source).forEach(name => {
if (name === 'constructor') return;
const descriptor = Object.getOwnPropertyDescriptor(source, name);
const newDescriptor = handleDescriptor(name, descriptor, target);
if (!config.mockInPlace || !target.hasOwnProperty(name)) {
Object.defineProperty(target, name, newDescriptor);
} else {
// When modifying original class, directly replace the method
if (typeof source[name] === 'function') {
target[name] = createMethodSpy(source[name], target, config);
} else {
Object.defineProperty(target, name, newDescriptor);
}
}
});
};
if (config.mockInPlace) {
// Modify the original class (when mockInPlace is true)
// First, process prototype methods
const proto = target.prototype;
Object.getOwnPropertyNames(proto).forEach(name => {
if (name === 'constructor') return;
const descriptor = Object.getOwnPropertyDescriptor(proto, name);
if (typeof proto[name] === 'function') {
(proto as any)[name] = createMethodSpy(proto[name], proto, config);
} else if (descriptor) {
Object.defineProperty(proto, name, handleDescriptor(name, descriptor, proto));
}
});
// Handle static members
Object.getOwnPropertyNames(target).forEach(name => {
if (name === 'length' || name === 'prototype' || name === 'name') return;
if (typeof (target as any)[name] === 'function') {
(target as any)[name] = createMethodSpy((target as any)[name], target, config);
} else {
const descriptor = Object.getOwnPropertyDescriptor(target, name);
if (descriptor) {
Object.defineProperty(target, name, handleDescriptor(name, descriptor, target));
}
}
});
// Handle static methods inheritance
handleStaticMethodInheritance(target, target, {debug: config.debug});
return target as unknown as ClsMock<T>;
}
// Create a new mock class (default behavior)
const MockClass = function (this: any, ...args: any[]) {

@@ -138,14 +201,2 @@ // Handle constructor call without new

// 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);
Object.defineProperty(target, name, handleDescriptor(name, descriptor, instance));
}
});
};
// Handle instance members

@@ -180,5 +231,5 @@ processMembers(instance, target.prototype);

// Handle static methods inheritance
handleStaticMethodInheritance(MockClass, target, {debug});
handleStaticMethodInheritance(MockClass, target, {debug: config.debug});
return MockClass as unknown as ClsMock<T>;
}

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

import {Config} from '../config';
import {ClsMock, DeepPartial, Fn, MockFunction} from '../types';
import {DEFAULT_CLASS_OPTIONS, DEFAULT_OBJECT_OPTIONS} from '../defaults';
import {ClassMockOptions, ClsMock, Fn, MockFunction, ObjectMockOptions} from '../types';
import * as clsModule from './class';

@@ -60,5 +60,3 @@ import * as fnModule from './function';

target: T,
options?: {
overrides?: DeepPartial<InstanceType<T>>;
} & Partial<Config>,
options?: ClassMockOptions<InstanceType<T>>,
): ClsMock<T>;

@@ -69,51 +67,50 @@

target: T,
options?: {
overrides?: DeepPartial<T>;
options?: ObjectMockOptions<T> & {
replace?: {
[K in keyof T]?: T[K] extends Fn ? Fn : never;
};
} & Partial<Config>,
},
): T;
// Partial implementation overload
export function compose<T extends object>(partialImpl: DeepPartial<T>, options?: Partial<Config>): T;
// Implementation
export function compose<T extends object>(
target?: T | DeepPartial<T> | (new (...args: any[]) => any),
options: {
overrides?: DeepPartial<T>;
target?: T | (new (...args: any[]) => any),
options: ObjectMockOptions<T> & {
replace?: {
[K in keyof T]?: T[K] extends Fn ? Fn : never;
};
} & Partial<Config> = {},
} = {},
): T | ClsMock<any> | MockFunction<any> {
// If no target is provided, create a function mock
if (target === undefined) {
if (!target) {
return fnModule.fn();
}
// If the target is a class constructor
if (typeof target === 'function' && 'prototype' in target) {
const classTarget = target as new (...args: any[]) => any;
return clsModule.cls(classTarget, options);
if (typeof target === 'function') {
const config = {
...DEFAULT_CLASS_OPTIONS,
...options,
overrides: {
...DEFAULT_CLASS_OPTIONS.overrides,
...options.overrides,
},
};
return clsModule.cls(target, config);
}
// Create object mock
const mockObj = objModule.obj(target as T, options);
const config = {
...DEFAULT_OBJECT_OPTIONS,
...options,
};
// Handle method replacements if specified
const result = objModule.obj(target, config);
if (options.replace) {
for (const [key, impl] of Object.entries(options.replace)) {
if (!(key in mockObj)) {
throw new Error(`Cannot replace non-existent method: ${key}`);
Object.entries(options.replace).forEach(([key, impl]) => {
if (impl && typeof impl === 'function') {
replaceModule.replace(result, key as keyof T, impl as unknown as Fn);
}
if (typeof impl !== 'function') {
throw new Error(`Replacement for method ${key} must be a function`);
}
replaceModule.replace(mockObj, key as keyof T, impl as Fn);
}
});
}
return mockObj;
return result;
}

@@ -0,209 +1,113 @@

import rfdc from 'rfdc';
import {createSpy} from '../spy';
import {Fn, MockFunction, MockObject, ObjMockOptions} from '../types';
import {Fn, MockObject} from '../types';
import {DeepPartial} from '../types/common';
import {isMocked} from '../utils/mock-handlers';
const clone = rfdc({circles: true, proto: true});
export interface ObjectMockOptions<T> {
/**
* Clone options:
* - undefined/false: modify original object (default)
* - true: shallow clone
* - 'deep': deep clone
*/
clone?: boolean | 'deep';
/** Optional overrides for specific properties or methods */
overrides?: DeepPartial<T>;
}
/**
* Creates a mock object with advanced mocking capabilities and tracking features.
* Creates a shallow clone of an object while preserving its prototype chain
*/
function shallowClone<T extends object>(obj: T): T {
const clone = Object.create(Object.getPrototypeOf(obj));
Object.getOwnPropertyNames(obj).forEach(key => {
const descriptor = Object.getOwnPropertyDescriptor(obj, key);
if (descriptor) {
Object.defineProperty(clone, key, descriptor);
}
});
return clone;
}
/**
* Creates a mock object by replacing methods with spies and tracking properties.
* Can either modify the original object or create a clone.
*
* Key features:
* 1. Deep mocking - Automatically mocks nested objects and methods
* 2. Spy tracking - Tracks all method calls while preserving original implementation
* 3. Property access control - Can control access to undefined properties
* 4. Mock utilities - Provides mockClear, mockReset, and mockRestore functionality
* 5. Prototype chain support - Properly handles prototype chain properties and methods
* 6. Getter/Setter support - Maintains getter/setter functionality in mocks
* 7. Circular reference handling - Safely handles circular object references
* 8. Type safety - Maintains TypeScript type information
*
* @template T - The object type to mock
* @param target - The object to mock
* @param options - Mock options including configuration
* @returns A proxy-based mock object that tracks all interactions
*
* @example
* ```typescript
* interface User {
* id: number;
* getName(): string;
* setName(name: string): void;
* }
*
* const mockUser = mock.obj<User>({
* id: 1,
* getName: () => 'John',
* setName: (name) => {}
* });
*
* mockUser.getName(); // Returns 'John'
* expect(mockUser.getName).toHaveBeenCalled();
* ```
* @returns A mock object that tracks interactions
*/
export function obj<T extends object>(target: T | undefined, options: ObjMockOptions<T> = {}): MockObject<T> {
const processedProperties = new Map<string | symbol, any>();
const originalImplementations = new Map<string | symbol, any>();
const processedObjects = new WeakSet(); // Track processed objects to handle circular references
const mock = {} as {[key: string]: any};
export function obj<T extends object>(target: T | undefined, options: ObjectMockOptions<T> = {}): MockObject<T> {
if (!target) {
return {} as MockObject<T>;
}
// Helper function to create a spy with proper initialization and context binding
const createSpyWithTracking = (fn?: Fn, context?: any): MockFunction<any> => {
const spy = createSpy(fn);
if (fn) {
spy.mockImplementation(function (this: any, ...args: any[]) {
return fn.apply(this === spy ? context : this, args);
});
}
return spy;
};
// Check if the object is already mocked
if (isMocked(target)) {
return target as MockObject<T>;
}
// Helper function to process nested objects and handle circular references
const processValue = (value: any, key: string | symbol): any => {
if (typeof value === 'function') {
const spy = createSpyWithTracking(value as Fn, mock);
originalImplementations.set(key, value);
return spy;
} else if (value && typeof value === 'object' && !Array.isArray(value)) {
// Check for circular references
if (processedObjects.has(value)) {
return processedProperties.get(key) || value;
}
// Create target object based on clone option
const targetWithMock: Record<string | symbol, any> =
options.clone === true ? shallowClone(target) : options.clone === 'deep' ? clone(target) : target;
// Mark object as processed to prevent infinite recursion
processedObjects.add(value);
// Process instance methods and properties
Object.entries(Object.getOwnPropertyDescriptors(target)).forEach(([key, descriptor]) => {
if (typeof descriptor.value === 'function') {
const method = descriptor.value;
const spy = createSpy();
// Create a new mock for nested object
const nestedMock = {} as {[key: string]: any};
// For arrow functions, bind to original target
// For regular functions, preserve dynamic this binding
const isArrowFn = !method.prototype;
const boundMethod = function (this: any, ...args: any[]) {
return method.apply(isArrowFn ? targetWithMock : this, args);
};
spy.mockImplementation(boundMethod);
// Process all properties of nested object
Object.entries(value).forEach(([k, v]) => {
if (typeof v === 'function') {
const spy = createSpyWithTracking(v as Fn, nestedMock);
nestedMock[k] = spy;
processedProperties.set(`${String(key)}.${k}`, spy);
originalImplementations.set(`${String(key)}.${k}`, v);
} else if (v && typeof v === 'object' && !Array.isArray(v)) {
nestedMock[k] = processValue(v, `${String(key)}.${k}`);
} else {
nestedMock[k] = v;
}
Object.defineProperty(targetWithMock, key, {
...descriptor,
value: spy,
});
// Apply mock overrides if provided
if (options.overrides && typeof options.overrides === 'object') {
const mockOverrides = (options.overrides as any)[key];
if (mockOverrides && typeof mockOverrides === 'object') {
Object.entries(mockOverrides).forEach(([k, v]) => {
if (typeof v === 'function') {
if (typeof nestedMock[k] === 'function') {
nestedMock[k].mockImplementation(v.bind(nestedMock));
} else {
nestedMock[k] = createSpyWithTracking(v as Fn, nestedMock);
processedProperties.set(`${String(key)}.${k}`, nestedMock[k]);
originalImplementations.set(`${String(key)}.${k}`, v);
}
} else if (v && typeof v === 'object' && !Array.isArray(v)) {
nestedMock[k] = processValue(v, `${String(key)}.${k}`);
} else {
nestedMock[k] = v;
}
});
}
}
// Add utility methods to nested mock
Object.assign(nestedMock, utilityMethods);
processedProperties.set(key, nestedMock);
return nestedMock;
}
return value;
};
});
// Helper function to get property descriptor from prototype chain
const getPropertyDescriptor = (obj: any, prop: string | symbol): PropertyDescriptor | undefined => {
let current = obj;
while (current) {
const descriptor = Object.getOwnPropertyDescriptor(current, prop);
if (descriptor) return descriptor;
current = Object.getPrototypeOf(current);
}
return undefined;
};
// Copy and mock prototype chain methods
let proto = Object.getPrototypeOf(target);
while (proto && proto !== Object.prototype) {
Object.entries(Object.getOwnPropertyDescriptors(proto)).forEach(([key, descriptor]) => {
// Only check if the property is not own property of targetWithMock
if (!Object.prototype.hasOwnProperty.call(targetWithMock, key) && typeof descriptor.value === 'function') {
const method = descriptor.value;
const spy = createSpy();
// Add utility methods
const utilityMethods = {
mockClear: function () {
processedProperties.forEach(value => {
if (typeof value === 'function' && value.mockClear) {
value.mockClear();
} else if (value && typeof value === 'object' && value.mockClear) {
value.mockClear();
}
});
},
mockReset: function () {
processedProperties.forEach(value => {
if (typeof value === 'function' && value.mockReset) {
value.mockReset();
} else if (value && typeof value === 'object' && value.mockReset) {
value.mockReset();
}
});
},
mockRestore: function () {
processedProperties.forEach((value, key) => {
if (typeof value === 'function') {
if (originalImplementations.has(key)) {
const original = originalImplementations.get(key);
value.mockImplementation(original);
}
} else if (value && typeof value === 'object' && value.mockRestore) {
value.mockRestore();
}
});
},
};
const isArrowFn = !method.prototype;
const boundMethod = function (this: any, ...args: any[]) {
return method.apply(isArrowFn ? targetWithMock : this, args);
};
spy.mockImplementation(boundMethod);
// Process original implementation if provided
if (target) {
// Copy instance properties
Object.entries(target).forEach(([key, value]) => {
if (typeof value === 'function') {
processedProperties.set(key, processValue(value, key));
} else if (value && typeof value === 'object' && !Array.isArray(value)) {
mock[key] = processValue(value, key);
} else {
mock[key] = value;
Object.defineProperty(targetWithMock, key, {
...descriptor,
value: spy,
});
}
});
// Process prototype chain
let proto = Object.getPrototypeOf(target);
while (proto && proto !== Object.prototype) {
Object.entries(Object.getOwnPropertyDescriptors(proto)).forEach(([key, descriptor]) => {
if (!processedProperties.has(key)) {
if (descriptor.value && typeof descriptor.value === 'function') {
processedProperties.set(key, processValue(descriptor.value, key));
} else if (descriptor.get || descriptor.set) {
Object.defineProperty(mock, key, {
get: descriptor.get?.bind(mock),
set: descriptor.set?.bind(mock),
enumerable: descriptor.enumerable,
configurable: true,
});
}
}
});
proto = Object.getPrototypeOf(proto);
}
proto = Object.getPrototypeOf(proto);
}
// Apply top-level mock overrides
if (options.overrides && typeof options.overrides === 'object') {
// Apply overrides if provided
if (options.overrides) {
Object.entries(options.overrides).forEach(([key, value]) => {
const current = processedProperties.get(key);
if (typeof value === 'function' && typeof current === 'function' && current.mockImplementation) {
current.mockImplementation(value.bind(mock));
} else if (value && typeof value === 'object' && !Array.isArray(value)) {
mock[key] = processValue(value, key);
if (typeof value === 'function') {
const spy = createSpy();
spy.mockImplementation(value as Fn);
targetWithMock[key] = spy;
} else {
processedProperties.set(key, processValue(value, key));
targetWithMock[key] = value;
}

@@ -213,97 +117,3 @@ });

// Create proxy handler
const handler: ProxyHandler<any> = {
get(target, prop) {
// Return utility methods directly
if (prop in utilityMethods) {
return utilityMethods[prop as keyof typeof utilityMethods];
}
// Return cached value if exists
if (processedProperties.has(prop)) {
return processedProperties.get(prop);
}
// Return instance property if exists
if (prop in mock) {
return (mock as any)[prop];
}
// Check prototype chain
if (target) {
const descriptor = getPropertyDescriptor(target, prop);
if (descriptor) {
if (typeof descriptor.value === 'function') {
const spy = processValue(descriptor.value, prop);
processedProperties.set(prop, spy);
return spy;
} else if (descriptor.get) {
return descriptor.get.call(mock);
}
}
}
// For properties not in original implementation
if (!options.allowUndefined) {
throw new Error(`Property ${String(prop)} is not defined in the original implementation`);
}
// Create a spy for unknown properties
const spy = createSpyWithTracking();
processedProperties.set(prop, spy);
return spy;
},
set(target, prop, value) {
if (options.strict && !(prop in mock) && !processedProperties.has(prop)) {
throw new Error(`Cannot add property ${String(prop)} in strict mode`);
}
if (typeof value === 'function') {
const spy = processValue(value, prop);
processedProperties.set(prop, spy);
mock[prop as string] = spy;
} else {
mock[prop as string] = value;
}
return true;
},
has(target, prop) {
return prop in mock || processedProperties.has(prop);
},
ownKeys() {
return [...new Set([...Object.keys(mock), ...processedProperties.keys()])];
},
getOwnPropertyDescriptor(target: object, prop: string | symbol): PropertyDescriptor {
if (prop in mock) {
return (
Object.getOwnPropertyDescriptor(mock, prop) || {
value: mock[prop as string],
writable: true,
enumerable: true,
configurable: true,
}
);
}
if (processedProperties.has(prop)) {
return {
value: processedProperties.get(prop),
writable: true,
enumerable: true,
configurable: true,
};
}
return {
value: undefined,
writable: true,
enumerable: true,
configurable: true,
};
},
};
return new Proxy(target || {}, handler) as MockObject<T>;
return targetWithMock as MockObject<T>;
}

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

import {Config} from '../config';
import {EXCLUDED_PROPS, ORIGINAL_PREFIX} from '../constants';
import {DEFAULT_FUNCTION_OPTIONS} from '../defaults';
import {createSpy} from '../spy';
import {Fn} from '../types';
import {Fn, FunctionMockOptions} from '../types';

@@ -44,11 +44,7 @@ /**

impl: Fn,
options: Partial<Config> = {},
options: FunctionMockOptions = {},
): void {
const config: Config = {
allowUndefined: options.allowUndefined ?? false,
strict: options.strict ?? false,
trackCalls: options.trackCalls ?? false,
debug: options.debug ?? false,
preservePrototype: options.preservePrototype ?? true,
timeout: options.timeout ?? 0,
const config: FunctionMockOptions = {
...DEFAULT_FUNCTION_OPTIONS,
...options,
};

@@ -55,0 +51,0 @@

@@ -34,3 +34,3 @@ import {Fn, MockFunction} from './types';

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

@@ -172,2 +172,7 @@ result = undefined as unknown as ReturnType<T>;

// If we have an initial implementation, use it
if (implementation) {
this.updateState({implementation});
}
this.spyFunction = boundSpy;

@@ -174,0 +179,0 @@ }

@@ -1,17 +0,15 @@

import type {Config} from '../config';
/**
* Basic function type
* Basic function type with generic arguments and return type
*/
export type Fn = (...args: any[]) => any;
export type Fn<TArgs extends any[] = any[], TReturn = any> = (...args: TArgs) => TReturn;
/**
* Async function type
* Async function type with generic arguments and return type
*/
export type AsyncFn = (...args: any[]) => Promise<any>;
export type AsyncFn<TArgs extends any[] = any[], TReturn = any> = (...args: TArgs) => Promise<TReturn>;
/**
* Class constructor type
* Class constructor type with generic arguments
*/
export type Constructor<T> = new (...args: any[]) => T;
export type Constructor<T, TArgs extends any[] = any[]> = new (...args: TArgs) => T;

@@ -26,51 +24,4 @@ /**

/**
* Object mock builder interface that provides fluent API for building mock objects
* Makes all properties optional recursively, including function return types
*/
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
*/
export type RecursivePartial<T> = {

@@ -87,36 +38,2 @@ [K in keyof T]?: T[K] extends Fn

/**
* Base configuration type for mocks
*/
export interface BaseConfig<T> {
returns?: T;
resolves?: Awaited<T>;
rejects?: any;
throws?: any;
delay?: number;
implementation?: (...args: any[]) => T;
}
/**
* Mock call information
*/
export type MockCall<T extends any[]> = {
args: T;
returnValue: any;
error?: any;
timestamp: number;
};
/**
* Mock behavior configuration
*/
export type MockBehaviorConfig<T> = BaseConfig<T>;
/**
* Mock behavior store
*/
export type MockBehavior<T> = MockBehaviorConfig<T> & {
callSpecific?: Map<number, MockBehaviorConfig<T>>;
};
/**
* Property descriptor type

@@ -133,3 +50,3 @@ */

/**
* Base matcher type
* Matcher interface for value comparison
*/

@@ -143,3 +60,3 @@ export interface Matcher<T = any> {

/**
* Mock matcher factory
* Factory for creating matchers
*/

@@ -146,0 +63,0 @@ export type MatcherFactory = {

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

import type {Config} from '../config';
import {cast, cls, compose, fn, obj, replace} from '../mocks';
import type {
AsyncFn,
BaseConfig,
Constructor,
DeepPartial,
Fn,
Matcher,
MatcherFactory,
PropertyDescriptor,
RecursivePartial,
} from './common';
import type {Constructor, DeepPartial, Fn, PropertyDescriptor, RecursivePartial} from './common';

@@ -26,2 +14,122 @@ /**

/**
* Base configuration options for all mock types
* These options control general mock behavior
*/
export interface BaseMockOptions {
/**
* Enables detailed logging of mock operations
* Useful for debugging and understanding mock behavior
* @default false
*/
debug?: boolean;
}
/**
* Options for handling circular references in complex objects
* Used by object and class mocks that might contain circular dependencies
*/
export interface CircularHandlingOptions {
/**
* Controls whether circular references should be handled
* When true, circular references are replaced with proxies
* @default true
*/
handleCircular?: boolean;
}
/**
* Options specific to function mocking
* Controls behavior tracking and spy functionality
*/
export interface FunctionMockOptions extends BaseMockOptions {
/**
* Enables call tracking for mocked functions
* Records arguments, return values, and call count
* @default false
*/
trackCalls?: boolean;
/**
* Automatically creates spies for all function properties
* Useful for tracking nested function calls
* @default false
*/
autoSpy?: boolean;
}
/**
* Options for mocking objects
* Controls object property behavior and implementation overrides
*/
export interface ObjectMockOptions<T = any> extends BaseMockOptions, CircularHandlingOptions {
/**
* Controls whether to mock methods from the object's prototype chain
* When true, prototype methods are also replaced with mocks
* @default false
*/
mockPrototype?: boolean;
/**
* Partial implementation to override specific properties or methods
* Allows customizing specific parts of the mock while keeping others default
* @default {}
*/
overrides?: DeepPartial<T>;
}
/**
* Options for mocking classes
* Controls class behavior, constructor, and static members
*/
export interface ClassMockOptions<T = any> extends ObjectMockOptions<T> {
/**
* When true, modifies the original class instead of creating a new one
* Use with caution as it affects the original class definition
* @default false
*/
mockInPlace?: boolean;
/**
* Preserves the original prototype chain of the class
* Important for instanceof checks and inheritance behavior
* @default true
*/
preservePrototype?: boolean;
/**
* Preserves the original constructor behavior
* When true, constructor calls are forwarded to the original class
* @default true
*/
preserveConstructor?: boolean;
/**
* Controls whether static class members should be mocked
* Includes static methods, properties, and getters/setters
* @default false
*/
mockStaticMembers?: boolean;
}
/**
* Options for type casting and partial implementations
* Controls how partial objects are converted to full mocks
*/
export interface CastMockOptions extends BaseMockOptions, CircularHandlingOptions {
/**
* Maintains the prototype chain when casting objects
* Important for preserving type information and inheritance
* @default true
*/
keepPrototype?: boolean;
/**
* Maximum time to wait for async operations in milliseconds
* Used when mocking async methods and properties
* @default 5000
*/
asyncTimeout?: number;
}
/**
* Mock function type with Jest-like chaining methods

@@ -86,11 +194,25 @@ */

interface BaseAdapter extends MockCapabilities {
getSpy(property: string): any;
getSpy<T extends Fn>(property: string): MockFunction<T>;
}
/**
* Mock adapter interface
* Mock adapter interface for framework-specific implementations
*/
export interface Adapter extends BaseAdapter {
/**
* Replaces a function on an object with a mock implementation
* @param obj The target object
* @param key The key of the function to replace
* @param fn The mock implementation
*/
replaceFn<T extends object>(obj: T, key: keyof T, fn: Fn): void;
createPropertySpy<T>(): {get?: MockFunction; set?: MockFunction};
/**
* Creates a spy for a property with getter and setter
* @returns An object containing mock functions for get and set operations
*/
createPropertySpy<Value>(): {
get?: MockFunction<() => Value>;
set?: MockFunction<(value: Value) => void>;
};
}

@@ -108,3 +230,3 @@

*/
export type MockCallBehavior<T extends Fn> = BaseConfig<ReturnType<T>> & {
export type MockCallBehavior<T extends Fn> = {
returns: (value: ReturnType<T>) => MockFunction<T>;

@@ -137,46 +259,2 @@ resolves: (value: Awaited<ReturnType<T>>) => MockFunction<T>;

/**
* Mock configuration options
*/
interface MockOptions {
modifyOriginal?: boolean;
mockPrototype?: boolean;
mockStatic?: boolean;
preserveConstructor?: boolean;
autoSpy?: boolean;
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
*/
export interface MockClassOptions
extends Pick<MockOptions, 'modifyOriginal' | 'mockPrototype' | 'mockStatic' | 'preserveConstructor'> {}
/**
* Mock object options
*/
export interface MockObjectOptions extends Pick<MockOptions, 'modifyOriginal' | 'mockPrototype'> {}
/**
* Mock result interface

@@ -242,33 +320,15 @@ */

/**
* Partial mock options
* Mock object interface that includes utility methods
*/
export interface PartialOptions<T> extends Pick<MockOptions, 'autoSpy' | 'handleCircular'> {
overrides?: DeepPartial<T>;
}
export type MockObject<T extends object> = T & {
mockClear(): void;
mockReset(): void;
mockRestore(): void;
mockImplementation(implementation: DeepPartial<T>): MockObject<T>;
mockReturnValue<TReturn>(value: TReturn): MockObject<T>;
mockResolvedValue<TReturn>(value: TReturn): MockObject<T>;
mockRejectedValue<TError>(value: TError): MockObject<T>;
};
/**
* Builder interface for partial mocks
*/
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>;
}
/**
* Instance mock interface for mocking objects and classes

@@ -278,13 +338,5 @@ */

/**
* 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>;
fn<T extends Fn>(options?: Partial<FunctionMockOptions>): MockFunction<T>;

@@ -294,3 +346,3 @@ /**

*/
obj<T extends object>(target: T | undefined, options?: ObjMockOptions<T>): MockObject<T>;
obj<T extends object>(target: T | undefined, options?: ObjectMockOptions<T>): MockObject<T>;

@@ -300,3 +352,3 @@ /**

*/
cls<T extends Constructor<any>>(target: T, options?: ClsMockOptions<T>): ClsMock<T>;
cls<T extends Constructor<any>>(target: T, options?: ClassMockOptions<T>): ClsMock<T>;

@@ -306,3 +358,3 @@ /**

*/
cast<T extends object>(partial: DeepPartial<T>, options?: Partial<Config>): T;
cast<T extends object>(partial: DeepPartial<T>, options?: CastMockOptions): T;

@@ -312,22 +364,9 @@ /**

*/
replace<T extends object, K extends keyof T>(obj: T, key: K, impl: Fn, options?: Partial<Config>): void;
replace<T extends object, K extends keyof T>(obj: T, key: K, impl: Fn, options?: Partial<FunctionMockOptions>): void;
/**
* Creates a mock from a class constructor
* Creates a mock from a class constructor or object
*/
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 Constructor<any>>(target: T, options?: ClassMockOptions<T>): ClsMock<T>;
compose<T extends object>(target: T, options?: ObjectMockOptions<T>): 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>;
};

@@ -5,2 +5,4 @@ import {DEFAULT_CONFIG} from '../../config';

describe('Async Utilities', () => {
const defaultTimeout = DEFAULT_CONFIG.timeout || 5000;
beforeEach(() => {

@@ -80,3 +82,2 @@ jest.useFakeTimers();

delay: 100,
preserveTiming: true,
});

@@ -136,2 +137,10 @@

});
it('should reject if operation takes too long', async () => {
const slowPromise = new Promise(resolve => setTimeout(resolve, defaultTimeout + 100));
const promise = withTimeout(slowPromise);
jest.advanceTimersByTime(defaultTimeout + 50);
await expect(promise).rejects.toThrow('Operation timed out');
});
});

@@ -138,0 +147,0 @@

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

import {MockError} from '../../errors';
import {createBuilder, createChainable} from '../chainable';

@@ -3,0 +2,0 @@

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

import {MockError, MockErrorCode} from '../errors';
// Default timeout value in milliseconds

@@ -23,4 +21,2 @@ const DEFAULT_TIMEOUT = 5000;

errorFactory?: () => Error;
/** Whether to preserve the original timing */
preserveTiming?: boolean;
}

@@ -40,3 +36,2 @@

errorFactory = () => new Error('Async operation failed'),
preserveTiming = false,
} = options;

@@ -43,0 +38,0 @@

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

import {MockError, MockErrorCode} from '../errors';
import {MethodTracker} from './method-tracker';

@@ -8,0 +7,0 @@

@@ -1,4 +0,4 @@

import {Config} from '../config';
import {DEFAULT_FUNCTION_OPTIONS} from '../defaults';
import {UniversalSpy} from '../spy';
import {Fn, MockFunction} from '../types';
import {Fn, FunctionMockOptions, MockFunction} from '../types';

@@ -16,3 +16,12 @@ /**

*/
export function createMethodSpy<T extends Fn>(method: T, context: any, options: Partial<Config> = {}): MockFunction<T> {
export function createMethodSpy<T extends Fn>(
method: T,
context: any,
options: Partial<FunctionMockOptions> = {},
): MockFunction<T> {
const _config = {
...DEFAULT_FUNCTION_OPTIONS,
...options,
};
const spy = new UniversalSpy<T>();

@@ -45,3 +54,3 @@ const spyFn = spy.getSpy();

context?: any,
options: Partial<Config> = {},
options: Partial<FunctionMockOptions> = {},
): {get?: MockFunction<() => T>; set?: MockFunction<(value: T) => void>} {

@@ -48,0 +57,0 @@ return {

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

/**
* Method call tracking and verification utilities with performance monitoring
*/
import {MockError} from '../errors';

@@ -6,0 +2,0 @@ import {compareArrays} from './compare';

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

trackAccess?: boolean;
/** Whether to allow undefined properties */
allowUndefined?: boolean;
/** Custom property getter */

@@ -35,3 +33,3 @@ getter?: (target: any, prop: string | symbol) => any;

export function createBaseHandler(cache: MockCache, options: ProxyHandlerOptions = {}): ProxyHandler<BaseTarget> {
const {trackAccess = false, allowUndefined = true, getter, setter} = options;
const {trackAccess = false, getter, setter} = options;

@@ -58,8 +56,3 @@ return {

const value = target[prop];
if (value === undefined && !allowUndefined) {
throw MockError.invalidImplementation(`Accessing undefined property: ${String(prop)}`);
}
return value;
return target[prop];
},

@@ -66,0 +59,0 @@

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