@corez/mock
Advanced tools
Comparing version 0.3.0 to 0.4.0
@@ -36,14 +36,26 @@ /** | ||
debug?: boolean; | ||
/** | ||
* Maximum time to wait for async operations in milliseconds | ||
* Used when mocking async methods and properties | ||
* @default 5000 | ||
*/ | ||
asyncTimeout?: number; | ||
} | ||
/** | ||
* Options for handling circular references in complex objects | ||
* Used by object and class mocks that might contain circular dependencies | ||
* Common options for object and class mocking | ||
* Controls basic mocking behavior | ||
*/ | ||
interface CircularHandlingOptions { | ||
interface CommonMockOptions<T = any> extends BaseMockOptions { | ||
/** | ||
* Controls whether circular references should be handled | ||
* When true, circular references are replaced with proxies | ||
* @default true | ||
* When true, modifies the original target instead of creating a new one | ||
* Use with caution as it affects the original definition | ||
* @default false | ||
*/ | ||
handleCircular?: boolean; | ||
mockInPlace?: boolean; | ||
/** | ||
* Partial implementation to override specific properties or methods | ||
* Allows customizing specific parts of the mock while keeping others default | ||
* @default {} | ||
*/ | ||
overrides?: DeepPartial<T>; | ||
} | ||
@@ -72,15 +84,10 @@ /** | ||
*/ | ||
interface ObjectMockOptions<T = any> extends BaseMockOptions, CircularHandlingOptions { | ||
interface ObjectMockOptions<T = any> extends CommonMockOptions<T> { | ||
/** | ||
* Controls whether to mock methods from the object's prototype chain | ||
* When true, prototype methods are also replaced with mocks | ||
* When true, mocks all nested objects and prototype chain methods | ||
* When false, only mocks the top level object's own methods | ||
* Only applies when mockInPlace is false | ||
* @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>; | ||
mockDeep?: boolean; | ||
} | ||
@@ -91,11 +98,5 @@ /** | ||
*/ | ||
interface ClassMockOptions<T = any> extends ObjectMockOptions<T> { | ||
interface ClassMockOptions<T = any> extends CommonMockOptions<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 | ||
* Controls whether to preserve the original prototype chain | ||
* Important for instanceof checks and inheritance behavior | ||
@@ -106,3 +107,3 @@ * @default true | ||
/** | ||
* Preserves the original constructor behavior | ||
* Controls whether to preserve the original constructor behavior | ||
* When true, constructor calls are forwarded to the original class | ||
@@ -113,7 +114,7 @@ * @default true | ||
/** | ||
* Controls whether static class members should be mocked | ||
* Controls whether to mock static class members | ||
* Includes static methods, properties, and getters/setters | ||
* @default false | ||
*/ | ||
mockStaticMembers?: boolean; | ||
mockStatic?: boolean; | ||
} | ||
@@ -124,3 +125,3 @@ /** | ||
*/ | ||
interface CastMockOptions extends BaseMockOptions, CircularHandlingOptions { | ||
interface CastMockOptions extends BaseMockOptions { | ||
/** | ||
@@ -132,8 +133,2 @@ * Maintains the prototype chain when casting objects | ||
keepPrototype?: boolean; | ||
/** | ||
* Maximum time to wait for async operations in milliseconds | ||
* Used when mocking async methods and properties | ||
* @default 5000 | ||
*/ | ||
asyncTimeout?: number; | ||
} | ||
@@ -263,3 +258,3 @@ /** | ||
* Default cast mock options | ||
* Controls type casting behavior and async operations | ||
* Controls type casting behavior | ||
*/ | ||
@@ -266,0 +261,0 @@ declare const DEFAULT_CAST_OPTIONS: CastMockOptions; |
@@ -11,7 +11,5 @@ 'use strict'; | ||
var DEFAULT_BASE_OPTIONS = { | ||
debug: false | ||
debug: false, | ||
asyncTimeout: 5e3 | ||
}; | ||
var DEFAULT_CIRCULAR_OPTIONS = { | ||
handleCircular: true | ||
}; | ||
var DEFAULT_FUNCTION_OPTIONS = { | ||
@@ -24,18 +22,17 @@ ...DEFAULT_BASE_OPTIONS, | ||
...DEFAULT_BASE_OPTIONS, | ||
...DEFAULT_CIRCULAR_OPTIONS, | ||
mockPrototype: false, | ||
mockInPlace: false, | ||
mockDeep: false, | ||
overrides: {} | ||
}; | ||
var DEFAULT_CLASS_OPTIONS = { | ||
...DEFAULT_OBJECT_OPTIONS, | ||
...DEFAULT_BASE_OPTIONS, | ||
mockInPlace: false, | ||
preservePrototype: true, | ||
preserveConstructor: true, | ||
mockStaticMembers: false, | ||
mockInPlace: false | ||
mockStatic: false, | ||
overrides: {} | ||
}; | ||
var DEFAULT_CAST_OPTIONS = { | ||
...DEFAULT_BASE_OPTIONS, | ||
...DEFAULT_CIRCULAR_OPTIONS, | ||
keepPrototype: true, | ||
asyncTimeout: 5e3 | ||
keepPrototype: true | ||
}; | ||
@@ -820,2 +817,10 @@ function createOptions(defaults, options = {}) { | ||
} | ||
function createMethodSpy2(method, mockTarget, isArrowFn) { | ||
const spy = createSpy(); | ||
const boundMethod = function(...args) { | ||
return method.apply(isArrowFn ? mockTarget : this, args); | ||
}; | ||
spy.mockImplementation(boundMethod); | ||
return spy; | ||
} | ||
function obj(target, options = {}) { | ||
@@ -828,13 +833,14 @@ if (!target) { | ||
} | ||
const targetWithMock = options.clone === true ? shallowClone(target) : options.clone === "deep" ? clone(target) : target; | ||
let mockTarget; | ||
if (options.mockInPlace) { | ||
mockTarget = target; | ||
} else { | ||
mockTarget = options.mockDeep ? clone(target) : shallowClone(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, { | ||
const spy = createMethodSpy2(method, mockTarget, isArrowFn); | ||
Object.defineProperty(mockTarget, key, { | ||
...descriptor, | ||
@@ -845,33 +851,32 @@ value: spy | ||
}); | ||
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 | ||
}); | ||
} | ||
}); | ||
proto = Object.getPrototypeOf(proto); | ||
if (options.mockDeep) { | ||
let proto = Object.getPrototypeOf(target); | ||
while (proto && proto !== Object.prototype) { | ||
Object.entries(Object.getOwnPropertyDescriptors(proto)).forEach(([key, descriptor]) => { | ||
if (!Object.prototype.hasOwnProperty.call(mockTarget, key) && typeof descriptor.value === "function") { | ||
const method = descriptor.value; | ||
const isArrowFn = !method.prototype; | ||
const spy = createMethodSpy2(method, mockTarget, isArrowFn); | ||
Object.defineProperty(mockTarget, key, { | ||
...descriptor, | ||
value: spy | ||
}); | ||
} | ||
}); | ||
proto = Object.getPrototypeOf(proto); | ||
} | ||
} | ||
if (options.overrides) { | ||
Object.entries(options.overrides).forEach(([key, value]) => { | ||
const propertyKey = key; | ||
if (typeof value === "function") { | ||
const spy = createSpy(); | ||
spy.mockImplementation(value); | ||
targetWithMock[key] = spy; | ||
mockTarget[propertyKey] = spy; | ||
} else { | ||
targetWithMock[key] = value; | ||
mockTarget[propertyKey] = value; | ||
} | ||
}); | ||
} | ||
return targetWithMock; | ||
return mockTarget; | ||
} | ||
@@ -878,0 +883,0 @@ |
@@ -5,7 +5,5 @@ import rfdc from 'rfdc'; | ||
var DEFAULT_BASE_OPTIONS = { | ||
debug: false | ||
debug: false, | ||
asyncTimeout: 5e3 | ||
}; | ||
var DEFAULT_CIRCULAR_OPTIONS = { | ||
handleCircular: true | ||
}; | ||
var DEFAULT_FUNCTION_OPTIONS = { | ||
@@ -18,18 +16,17 @@ ...DEFAULT_BASE_OPTIONS, | ||
...DEFAULT_BASE_OPTIONS, | ||
...DEFAULT_CIRCULAR_OPTIONS, | ||
mockPrototype: false, | ||
mockInPlace: false, | ||
mockDeep: false, | ||
overrides: {} | ||
}; | ||
var DEFAULT_CLASS_OPTIONS = { | ||
...DEFAULT_OBJECT_OPTIONS, | ||
...DEFAULT_BASE_OPTIONS, | ||
mockInPlace: false, | ||
preservePrototype: true, | ||
preserveConstructor: true, | ||
mockStaticMembers: false, | ||
mockInPlace: false | ||
mockStatic: false, | ||
overrides: {} | ||
}; | ||
var DEFAULT_CAST_OPTIONS = { | ||
...DEFAULT_BASE_OPTIONS, | ||
...DEFAULT_CIRCULAR_OPTIONS, | ||
keepPrototype: true, | ||
asyncTimeout: 5e3 | ||
keepPrototype: true | ||
}; | ||
@@ -814,2 +811,10 @@ function createOptions(defaults, options = {}) { | ||
} | ||
function createMethodSpy2(method, mockTarget, isArrowFn) { | ||
const spy = createSpy(); | ||
const boundMethod = function(...args) { | ||
return method.apply(isArrowFn ? mockTarget : this, args); | ||
}; | ||
spy.mockImplementation(boundMethod); | ||
return spy; | ||
} | ||
function obj(target, options = {}) { | ||
@@ -822,13 +827,14 @@ if (!target) { | ||
} | ||
const targetWithMock = options.clone === true ? shallowClone(target) : options.clone === "deep" ? clone(target) : target; | ||
let mockTarget; | ||
if (options.mockInPlace) { | ||
mockTarget = target; | ||
} else { | ||
mockTarget = options.mockDeep ? clone(target) : shallowClone(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, { | ||
const spy = createMethodSpy2(method, mockTarget, isArrowFn); | ||
Object.defineProperty(mockTarget, key, { | ||
...descriptor, | ||
@@ -839,33 +845,32 @@ value: spy | ||
}); | ||
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 | ||
}); | ||
} | ||
}); | ||
proto = Object.getPrototypeOf(proto); | ||
if (options.mockDeep) { | ||
let proto = Object.getPrototypeOf(target); | ||
while (proto && proto !== Object.prototype) { | ||
Object.entries(Object.getOwnPropertyDescriptors(proto)).forEach(([key, descriptor]) => { | ||
if (!Object.prototype.hasOwnProperty.call(mockTarget, key) && typeof descriptor.value === "function") { | ||
const method = descriptor.value; | ||
const isArrowFn = !method.prototype; | ||
const spy = createMethodSpy2(method, mockTarget, isArrowFn); | ||
Object.defineProperty(mockTarget, key, { | ||
...descriptor, | ||
value: spy | ||
}); | ||
} | ||
}); | ||
proto = Object.getPrototypeOf(proto); | ||
} | ||
} | ||
if (options.overrides) { | ||
Object.entries(options.overrides).forEach(([key, value]) => { | ||
const propertyKey = key; | ||
if (typeof value === "function") { | ||
const spy = createSpy(); | ||
spy.mockImplementation(value); | ||
targetWithMock[key] = spy; | ||
mockTarget[propertyKey] = spy; | ||
} else { | ||
targetWithMock[key] = value; | ||
mockTarget[propertyKey] = value; | ||
} | ||
}); | ||
} | ||
return targetWithMock; | ||
return mockTarget; | ||
} | ||
@@ -872,0 +877,0 @@ |
{ | ||
"name": "@corez/mock", | ||
"version": "0.3.0", | ||
"version": "0.4.0", | ||
"description": "A powerful and flexible TypeScript mocking library for testing", | ||
@@ -5,0 +5,0 @@ "keywords": [ |
@@ -55,2 +55,7 @@ # @corez/mock | ||
// Mock an object | ||
interface User { | ||
id: number; | ||
name: string; | ||
} | ||
interface UserService { | ||
@@ -85,14 +90,8 @@ getUser(id: number): Promise<User>; | ||
const MockDatabase = mock.cls(Database, { | ||
overrides: { | ||
query: async () => [{id: 1}], | ||
}, | ||
mockStatic: true, | ||
preserveConstructor: true, | ||
}); | ||
// Or modify the original class | ||
mock.cls(Database, { | ||
mockInPlace: true, | ||
overrides: { | ||
query: async () => [{id: 1}], | ||
}, | ||
}); | ||
const db = new MockDatabase(); | ||
db.query.mockResolvedValue({rows: []}); | ||
``` | ||
@@ -134,14 +133,14 @@ | ||
const userService = mock.obj<UserService>( | ||
{ | ||
getUser: async id => ({id, name: 'John'}), | ||
updateUser: async user => {}, | ||
}, | ||
{ | ||
overrides: { | ||
getUser: async id => ({id, name: 'Mock User'}), | ||
}, | ||
}, | ||
); | ||
const userService = mock.obj<UserService>({ | ||
getUser: async id => ({id, name: 'John'}), | ||
updateUser: async user => {}, | ||
}); | ||
// Track method calls | ||
userService.getUser(1); | ||
expect((userService.getUser as any).mock.calls.length).toBe(1); | ||
// Override return values | ||
(userService.getUser as any).mockResolvedValue({id: 1, name: 'Mock'}); | ||
// Access call information | ||
@@ -534,13 +533,16 @@ const getUserMock = userService.getUser as MockFunction; | ||
- Enable `trackCalls` in config | ||
- Ensure mock is properly created | ||
- For functions: Enable `trackCalls` in FunctionMockOptions | ||
- For objects and classes: Method calls are tracked automatically | ||
- Ensure you're accessing the mock properties correctly (e.g., `mock.calls`) | ||
2. **Type Inference Issues** | ||
- Use explicit type parameters | ||
- Define interfaces for complex types | ||
- Use explicit type parameters with interfaces | ||
- Define complete interfaces for complex types | ||
- Use `as MockFunction<T>` for better type support | ||
3. **Prototype Chain Issues** | ||
- Enable `preservePrototype` | ||
- Use `mock.cls()` for classes | ||
- For classes: Use `preservePrototype: true` (default) | ||
- For objects: Use `mockDeep: true` to mock prototype methods | ||
- For type casting: Use `keepPrototype: true` (default) | ||
@@ -562,2 +564,3 @@ ## Contributing | ||
- `debug`: Enables detailed logging of mock operations (default: false) | ||
- `asyncTimeout`: Maximum time to wait for async operations in ms (default: 5000) | ||
@@ -575,4 +578,5 @@ ### Function Mock Options | ||
- `handleCircular`: Handles circular references in complex objects (default: true) | ||
- `mockPrototype`: Controls whether to mock methods from the prototype chain (default: false) | ||
- `mockInPlace`: Controls whether to modify the original object or create a new one (default: false) | ||
- `mockDeep`: Controls whether to perform deep cloning and mocking of nested objects and prototype chain methods | ||
(default: false) | ||
- `overrides`: Allows overriding specific properties or methods with custom implementations | ||
@@ -582,8 +586,9 @@ | ||
Options for mocking classes (extends Object Mock Options): | ||
Options for mocking classes: | ||
- `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) | ||
- `mockInPlace`: Controls whether to modify the original class or create a new one (default: false) | ||
- `preservePrototype`: Controls whether to preserve the prototype chain (default: true) | ||
- `preserveConstructor`: Controls whether to preserve the original constructor behavior (default: true) | ||
- `mockStatic`: Controls whether to mock static class members (default: false) | ||
- `overrides`: Allows overriding specific properties or methods with custom implementations | ||
@@ -594,5 +599,3 @@ ### Cast Mock Options | ||
- `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) | ||
- `keepPrototype`: Maintains the prototype chain when casting objects (default: true) | ||
@@ -604,3 +607,3 @@ ## Usage Examples | ||
```typescript | ||
import {mock} from 'hexxspark'; | ||
import {mock} from '@corez/mock'; | ||
@@ -619,3 +622,3 @@ // Create a mock function | ||
```typescript | ||
import {mock} from 'hexxspark'; | ||
import {mock} from '@corez/mock'; | ||
@@ -640,3 +643,3 @@ interface User { | ||
```typescript | ||
import {mock} from 'hexxspark'; | ||
import {mock} from '@corez/mock'; | ||
@@ -654,3 +657,3 @@ class Database { | ||
const MockDatabase = mock.cls(Database, { | ||
mockStaticMembers: true, | ||
mockStatic: true, | ||
preserveConstructor: true, | ||
@@ -666,3 +669,3 @@ }); | ||
```typescript | ||
import {mock} from 'hexxspark'; | ||
import {mock} from '@corez/mock'; | ||
@@ -676,5 +679,3 @@ interface ComplexType { | ||
const partial = {data: 'test'}; | ||
const mockObj = mock.cast<ComplexType>(partial, { | ||
asyncTimeout: 1000, | ||
}); | ||
const mockObj = mock.cast<ComplexType>(partial); | ||
``` |
export interface Config { | ||
preservePrototype?: boolean; | ||
mockPrototype?: boolean; | ||
timeout: number; | ||
@@ -9,4 +8,3 @@ } | ||
preservePrototype: true, | ||
mockPrototype: false, | ||
timeout: 5000, | ||
}; |
@@ -1,9 +0,2 @@ | ||
import { | ||
BaseMockOptions, | ||
CastMockOptions, | ||
CircularHandlingOptions, | ||
ClassMockOptions, | ||
FunctionMockOptions, | ||
ObjectMockOptions, | ||
} from './types'; | ||
import {BaseMockOptions, CastMockOptions, ClassMockOptions, FunctionMockOptions, ObjectMockOptions} from './types'; | ||
@@ -16,13 +9,6 @@ /** | ||
debug: false, | ||
asyncTimeout: 5000, | ||
}; | ||
/** | ||
* Default circular handling options | ||
* Used by object and class mocks that might contain circular references | ||
*/ | ||
export const DEFAULT_CIRCULAR_OPTIONS: CircularHandlingOptions = { | ||
handleCircular: true, | ||
}; | ||
/** | ||
* Default function mock options | ||
@@ -43,4 +29,4 @@ * Controls function tracking and spy behavior | ||
...DEFAULT_BASE_OPTIONS, | ||
...DEFAULT_CIRCULAR_OPTIONS, | ||
mockPrototype: false, | ||
mockInPlace: false, | ||
mockDeep: false, | ||
overrides: {}, | ||
@@ -54,7 +40,8 @@ }; | ||
export const DEFAULT_CLASS_OPTIONS: ClassMockOptions = { | ||
...DEFAULT_OBJECT_OPTIONS, | ||
...DEFAULT_BASE_OPTIONS, | ||
mockInPlace: false, | ||
preservePrototype: true, | ||
preserveConstructor: true, | ||
mockStaticMembers: false, | ||
mockInPlace: false, | ||
mockStatic: false, | ||
overrides: {}, | ||
}; | ||
@@ -64,9 +51,7 @@ | ||
* Default cast mock options | ||
* Controls type casting behavior and async operations | ||
* Controls type casting behavior | ||
*/ | ||
export const DEFAULT_CAST_OPTIONS: CastMockOptions = { | ||
...DEFAULT_BASE_OPTIONS, | ||
...DEFAULT_CIRCULAR_OPTIONS, | ||
keepPrototype: true, | ||
asyncTimeout: 5000, | ||
}; | ||
@@ -73,0 +58,0 @@ |
@@ -1,2 +0,1 @@ | ||
import {MockFunction} from '../../types'; | ||
import {obj} from '..'; | ||
@@ -41,205 +40,5 @@ | ||
describe('arrow functions', () => { | ||
describe('cloning behavior', () => { | ||
class TestClass { | ||
private counter = 0; | ||
// Regular method: this binding depends on call site | ||
increment() { | ||
this.counter++; | ||
return this.counter; | ||
} | ||
// Arrow method: this is always bound to instance | ||
incrementArrow = () => { | ||
this.counter++; | ||
return this.counter; | ||
}; | ||
getCounter() { | ||
return this.counter; | ||
} | ||
} | ||
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 handle regular method this binding', () => { | ||
const instance = new TestClass(); | ||
const mock = obj(instance); | ||
// 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 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: { | ||
incrementArrow: () => 999, | ||
}, | ||
}); | ||
expect(mock.incrementArrow()).toBe(999); | ||
expect(mock.getCounter()).toBe(0); // Original counter should not change | ||
}); | ||
}); | ||
describe('method tracking', () => { | ||
interface Calculator { | ||
add(a: number, b: number): number; | ||
multiply(a: number, b: number): number; | ||
} | ||
let calculator: Calculator; | ||
beforeEach(() => { | ||
calculator = { | ||
add: (a: number, b: number) => a + b, | ||
multiply: (a: number, b: number) => a * b, | ||
}; | ||
}); | ||
it('should track method calls with arguments', () => { | ||
const mock = obj(calculator); | ||
mock.add(2, 3); | ||
mock.multiply(4, 5); | ||
expect(mock.add as MockFunction<typeof calculator.add>).toHaveBeenCalledWith(2, 3); | ||
expect(mock.multiply as MockFunction<typeof calculator.multiply>).toHaveBeenCalledWith(4, 5); | ||
}); | ||
it('should track call count', () => { | ||
const mock = obj(calculator); | ||
mock.add(1, 2); | ||
mock.add(3, 4); | ||
mock.multiply(2, 3); | ||
expect((mock.add as MockFunction<typeof calculator.add>).calls.count()).toBe(2); | ||
expect((mock.multiply as MockFunction<typeof calculator.multiply>).calls.count()).toBe(1); | ||
}); | ||
it('should clear call history', () => { | ||
const mock = obj(calculator); | ||
mock.add(1, 2); | ||
(mock.add as MockFunction<typeof calculator.add>).mockClear(); | ||
expect((mock.add as MockFunction<typeof calculator.add>).calls.count()).toBe(0); | ||
}); | ||
}); | ||
describe('method precedence', () => { | ||
class BaseClass { | ||
getValue() { | ||
return 'base'; | ||
} | ||
} | ||
class DerivedClass extends BaseClass { | ||
constructor() { | ||
super(); | ||
// Override getValue on instance | ||
this.getValue = () => 'instance'; | ||
} | ||
} | ||
it('should prioritize instance methods over prototype methods', () => { | ||
const instance = new DerivedClass(); | ||
const mock = obj(instance); | ||
expect(mock.getValue()).toBe('instance'); | ||
expect(mock.getValue).toHaveBeenCalled(); | ||
}); | ||
}); | ||
describe('prototype chain handling', () => { | ||
class BaseClass { | ||
baseMethod() { | ||
return 'base'; | ||
} | ||
} | ||
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 allow overriding prototype methods without affecting other instances', () => { | ||
const instance1 = new ChildClass(); | ||
const instance2 = new ChildClass(); | ||
const mock = obj(instance1, { | ||
overrides: { | ||
baseMethod: () => 'mocked base', | ||
}, | ||
}); | ||
// 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'); | ||
}); | ||
}); | ||
describe('clone option', () => { | ||
class TestClass { | ||
private counter = 0; | ||
public name: string; | ||
@@ -251,3 +50,2 @@ | ||
// Regular method | ||
increment() { | ||
@@ -258,8 +56,2 @@ this.counter++; | ||
// Arrow method | ||
incrementArrow = () => { | ||
this.counter++; | ||
return this.counter; | ||
}; | ||
getCounter() { | ||
@@ -274,98 +66,25 @@ return this.counter; | ||
it('should modify original object by default', () => { | ||
it('should create new instance by default', () => { | ||
const original = new TestClass('test'); | ||
const mock = obj(original); | ||
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); | ||
}); | ||
it('should track calls on original object when not cloned', () => { | ||
const original = new TestClass('test'); | ||
const mock = obj(original); | ||
mock.increment(); | ||
mock.incrementArrow(); | ||
expect(mock.increment).toHaveBeenCalledTimes(1); | ||
expect(mock.incrementArrow).toHaveBeenCalledTimes(1); | ||
expect(original.increment).toHaveProperty('mockImplementation'); | ||
expect(original.incrementArrow).toHaveProperty('mockImplementation'); | ||
}); | ||
it('should allow overrides on original object', () => { | ||
const original = new TestClass('test'); | ||
const mock = obj(original, { | ||
overrides: { | ||
increment: () => 999, | ||
getName: () => 'overridden', | ||
}, | ||
}); | ||
expect(mock.increment()).toBe(999); | ||
expect(mock.getName()).toBe('overridden'); | ||
expect(original.increment()).toBe(999); // Original should be modified | ||
expect(original.getName()).toBe('overridden'); | ||
}); | ||
it('should create a new object instance when clone is true', () => { | ||
const original = new TestClass('test'); | ||
const mock = obj(original, {clone: true}); | ||
expect(mock).not.toBe(original); | ||
expect(mock.getName()).toBe('test'); | ||
expect(Object.getPrototypeOf(mock)).toBe(Object.getPrototypeOf(original)); | ||
}); | ||
it('should maintain property descriptors in cloned object', () => { | ||
it('should modify original when mockInPlace is true', () => { | ||
const original = new TestClass('test'); | ||
Object.defineProperty(original, 'readOnly', { | ||
value: 'constant', | ||
writable: false, | ||
configurable: false, | ||
}); | ||
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'); | ||
const mock = obj(original, {mockInPlace: true}); | ||
expect(mock).toBe(original); | ||
}); | ||
it('should preserve arrow function this binding in cloned object', () => { | ||
it('should create shallow clone when not mocking in place', () => { | ||
const original = new TestClass('test'); | ||
const mock = obj(original, {clone: true}); | ||
// 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 preserve regular method this binding in cloned object', () => { | ||
const original = new TestClass('test'); | ||
const mock = obj(original, {clone: true}); | ||
// Method called directly should still work | ||
const method = mock.increment; | ||
method(); | ||
expect(mock.getCounter()).toBe(1); | ||
// Method called through object should work too | ||
const mock = obj(original, {mockInPlace: false}); | ||
mock.increment(); | ||
expect(mock.getCounter()).toBe(2); | ||
expect(original.getCounter()).toBe(0); // Original should be unchanged | ||
}); | ||
it('should handle nested objects in clone', () => { | ||
it('should handle deep cloning', () => { | ||
const original = { | ||
nested: { | ||
value: 42, | ||
value: 123, | ||
method() { | ||
@@ -377,188 +96,20 @@ return this.value; | ||
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 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: { | ||
increment: () => 999, | ||
getName: () => 'overridden', | ||
}, | ||
}); | ||
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'); | ||
}); | ||
it('should preserve prototype chain in cloned object', () => { | ||
class ChildClass extends TestClass { | ||
childMethod() { | ||
return 'child'; | ||
} | ||
} | ||
const original = new ChildClass('test'); | ||
const mock = obj(original, {clone: true}); | ||
expect(mock instanceof ChildClass).toBe(true); | ||
expect(mock instanceof TestClass).toBe(true); | ||
expect(mock.childMethod()).toBe('child'); | ||
expect(mock.getName()).toBe('test'); | ||
}); | ||
it('should handle getters and setters in cloned object', () => { | ||
class WithAccessors { | ||
private _value = 0; | ||
get value() { | ||
return this._value; | ||
} | ||
set value(v: number) { | ||
this._value = v; | ||
} | ||
} | ||
const original = new WithAccessors(); | ||
const mock = obj(original, {clone: true}); | ||
mock.value = 42; | ||
expect(mock.value).toBe(42); | ||
expect(original.value).toBe(0); // Original should be unchanged | ||
}); | ||
it('should handle circular references in deep clone', () => { | ||
interface CircularObj { | ||
id: number; | ||
name: string; | ||
method(): string; | ||
self?: CircularObj; | ||
} | ||
const circular: CircularObj = { | ||
name: 'parent', | ||
id: 1, | ||
method() { | ||
return this.name; | ||
}, | ||
}; | ||
circular.self = circular; | ||
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 preserve method behavior with circular references', () => { | ||
interface CircularObj { | ||
id: number; | ||
counter: number; | ||
self?: CircularObj; | ||
increment(): number; | ||
} | ||
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; | ||
const mock = obj(original, {clone: 'deep'}); | ||
// Verify method behavior | ||
const result = mock.increment(); | ||
expect(result).toBe(1); | ||
expect(mock.counter).toBe(1); | ||
expect(original.counter).toBe(0); | ||
// Verify method call tracing | ||
expect(mock.increment).toHaveBeenCalledTimes(1); | ||
// Verify circular references | ||
expect(mock.self === mock).toBe(true); | ||
}); | ||
}); | ||
describe('clone options', () => { | ||
const createNestedObject = () => ({ | ||
nested: { | ||
value: 42, | ||
method() { | ||
return this.value; | ||
}, | ||
}, | ||
}); | ||
it('should modify original object by default', () => { | ||
const original = createNestedObject(); | ||
const mock = obj(original); | ||
expect(mock).toBe(original); | ||
expect(mock.nested).toBe(original.nested); | ||
expect(mock.nested.method()).toBe(42); | ||
}); | ||
it('should create shallow clone when clone is true', () => { | ||
const original = createNestedObject(); | ||
const mock = obj(original, {clone: true}); | ||
const mock = obj(original, {mockDeep: true}); | ||
expect(mock).not.toBe(original); | ||
expect(mock.nested).toBe(original.nested); | ||
expect(mock.nested.method()).toBe(42); | ||
}); | ||
it('should create deep clone when clone is "deep"', () => { | ||
const original = createNestedObject(); | ||
const mock = obj(original, {clone: 'deep'}); | ||
expect(mock).not.toBe(original); | ||
expect(mock.nested).not.toBe(original.nested); | ||
expect(mock.nested.method()).toBe(42); | ||
expect(mock.nested.value).toBe(123); | ||
expect(mock.nested.method()).toBe(123); | ||
}); | ||
it('should handle circular references in deep clone', () => { | ||
interface CircularObj { | ||
it('should handle circular references with deep clone', () => { | ||
type CircularObj = { | ||
id: number; | ||
name: string; | ||
method(): string; | ||
self?: CircularObj; | ||
} | ||
getValue(): number; | ||
}; | ||
const circular: CircularObj = { | ||
name: 'parent', | ||
id: 1, | ||
method() { | ||
return this.name; | ||
getValue() { | ||
return this.id; | ||
}, | ||
@@ -568,92 +119,9 @@ }; | ||
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); | ||
const mock = obj(circular, {mockDeep: true}); | ||
expect(mock).not.toBe(circular); | ||
expect(mock.self).toBe(mock); | ||
expect(mock.getValue()).toBe(1); | ||
expect(mock.id).toBe(1); | ||
}); | ||
it('should preserve method behavior with circular references', () => { | ||
interface CircularObj { | ||
id: number; | ||
counter: number; | ||
self?: CircularObj; | ||
increment(): number; | ||
} | ||
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; | ||
const mock = obj(original, {clone: 'deep'}); | ||
// Verify method behavior | ||
const result = mock.increment(); | ||
expect(result).toBe(1); | ||
expect(mock.counter).toBe(1); | ||
expect(original.counter).toBe(0); | ||
// Verify method call tracing | ||
expect(mock.increment).toHaveBeenCalledTimes(1); | ||
// Verify circular references | ||
expect(mock.self === mock).toBe(true); | ||
}); | ||
}); | ||
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; | ||
}; | ||
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); | ||
}); | ||
}); | ||
}); |
import rfdc from 'rfdc'; | ||
import {createSpy} from '../spy'; | ||
import {Fn, MockObject} from '../types'; | ||
import {DeepPartial} from '../types/common'; | ||
import {Fn, MockObject, ObjectMockOptions} from '../types'; | ||
import {isMocked} from '../utils/mock-handlers'; | ||
@@ -10,14 +9,2 @@ | ||
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>; | ||
} | ||
/** | ||
@@ -38,2 +25,14 @@ * Creates a shallow clone of an object while preserving its prototype chain | ||
/** | ||
* Creates a spy for a method while preserving its this binding | ||
*/ | ||
function createMethodSpy<T extends object>(method: Fn, mockTarget: T, isArrowFn: boolean): MockObject<Fn> { | ||
const spy = createSpy(); | ||
const boundMethod = function (this: T, ...args: unknown[]) { | ||
return method.apply(isArrowFn ? mockTarget : this, args); | ||
}; | ||
spy.mockImplementation(boundMethod); | ||
return spy; | ||
} | ||
/** | ||
* Creates a mock object by replacing methods with spies and tracking properties. | ||
@@ -57,6 +56,10 @@ * Can either modify the original object or create a clone. | ||
// Create target object based on clone option | ||
const targetWithMock: Record<string | symbol, any> = | ||
options.clone === true ? shallowClone(target) : options.clone === 'deep' ? clone(target) : target; | ||
let mockTarget: T; | ||
if (options.mockInPlace) { | ||
mockTarget = target; | ||
} else { | ||
mockTarget = options.mockDeep ? clone(target) : shallowClone(target); | ||
} | ||
// Process instance methods and properties | ||
@@ -66,13 +69,6 @@ Object.entries(Object.getOwnPropertyDescriptors(target)).forEach(([key, descriptor]) => { | ||
const method = descriptor.value; | ||
const spy = createSpy(); | ||
// 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); | ||
const spy = createMethodSpy(method, mockTarget, isArrowFn); | ||
Object.defineProperty(targetWithMock, key, { | ||
Object.defineProperty(mockTarget, key, { | ||
...descriptor, | ||
@@ -84,24 +80,21 @@ value: spy, | ||
// 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(); | ||
// Mock prototype chain methods if doing deep mocking | ||
if (options.mockDeep) { | ||
let proto = Object.getPrototypeOf(target); | ||
while (proto && proto !== Object.prototype) { | ||
Object.entries(Object.getOwnPropertyDescriptors(proto)).forEach(([key, descriptor]) => { | ||
// Only mock methods that haven't been mocked yet | ||
if (!Object.prototype.hasOwnProperty.call(mockTarget, key) && typeof descriptor.value === 'function') { | ||
const method = descriptor.value; | ||
const isArrowFn = !method.prototype; | ||
const spy = createMethodSpy(method, mockTarget, isArrowFn); | ||
const isArrowFn = !method.prototype; | ||
const boundMethod = function (this: any, ...args: any[]) { | ||
return method.apply(isArrowFn ? targetWithMock : this, args); | ||
}; | ||
spy.mockImplementation(boundMethod); | ||
Object.defineProperty(targetWithMock, key, { | ||
...descriptor, | ||
value: spy, | ||
}); | ||
} | ||
}); | ||
proto = Object.getPrototypeOf(proto); | ||
Object.defineProperty(mockTarget, key, { | ||
...descriptor, | ||
value: spy, | ||
}); | ||
} | ||
}); | ||
proto = Object.getPrototypeOf(proto); | ||
} | ||
} | ||
@@ -112,8 +105,9 @@ | ||
Object.entries(options.overrides).forEach(([key, value]) => { | ||
const propertyKey = key as keyof T; | ||
if (typeof value === 'function') { | ||
const spy = createSpy(); | ||
spy.mockImplementation(value as Fn); | ||
targetWithMock[key] = spy; | ||
mockTarget[propertyKey] = spy as T[keyof T]; | ||
} else { | ||
targetWithMock[key] = value; | ||
mockTarget[propertyKey] = value as T[keyof T]; | ||
} | ||
@@ -123,3 +117,3 @@ }); | ||
return targetWithMock as MockObject<T>; | ||
return mockTarget as MockObject<T>; | ||
} |
@@ -24,15 +24,29 @@ import type {Constructor, DeepPartial, Fn, PropertyDescriptor, RecursivePartial} from './common'; | ||
debug?: boolean; | ||
/** | ||
* Maximum time to wait for async operations in milliseconds | ||
* Used when mocking async methods and properties | ||
* @default 5000 | ||
*/ | ||
asyncTimeout?: number; | ||
} | ||
/** | ||
* Options for handling circular references in complex objects | ||
* Used by object and class mocks that might contain circular dependencies | ||
* Common options for object and class mocking | ||
* Controls basic mocking behavior | ||
*/ | ||
export interface CircularHandlingOptions { | ||
export interface CommonMockOptions<T = any> extends BaseMockOptions { | ||
/** | ||
* Controls whether circular references should be handled | ||
* When true, circular references are replaced with proxies | ||
* @default true | ||
* When true, modifies the original target instead of creating a new one | ||
* Use with caution as it affects the original definition | ||
* @default false | ||
*/ | ||
handleCircular?: boolean; | ||
mockInPlace?: boolean; | ||
/** | ||
* Partial implementation to override specific properties or methods | ||
* Allows customizing specific parts of the mock while keeping others default | ||
* @default {} | ||
*/ | ||
overrides?: DeepPartial<T>; | ||
} | ||
@@ -64,16 +78,10 @@ | ||
*/ | ||
export interface ObjectMockOptions<T = any> extends BaseMockOptions, CircularHandlingOptions { | ||
export interface ObjectMockOptions<T = any> extends CommonMockOptions<T> { | ||
/** | ||
* Controls whether to mock methods from the object's prototype chain | ||
* When true, prototype methods are also replaced with mocks | ||
* When true, mocks all nested objects and prototype chain methods | ||
* When false, only mocks the top level object's own methods | ||
* Only applies when mockInPlace is false | ||
* @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>; | ||
mockDeep?: boolean; | ||
} | ||
@@ -85,12 +93,5 @@ | ||
*/ | ||
export interface ClassMockOptions<T = any> extends ObjectMockOptions<T> { | ||
export interface ClassMockOptions<T = any> extends CommonMockOptions<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 | ||
* Controls whether to preserve the original prototype chain | ||
* Important for instanceof checks and inheritance behavior | ||
@@ -102,3 +103,3 @@ * @default true | ||
/** | ||
* Preserves the original constructor behavior | ||
* Controls whether to preserve the original constructor behavior | ||
* When true, constructor calls are forwarded to the original class | ||
@@ -110,7 +111,7 @@ * @default true | ||
/** | ||
* Controls whether static class members should be mocked | ||
* Controls whether to mock static class members | ||
* Includes static methods, properties, and getters/setters | ||
* @default false | ||
*/ | ||
mockStaticMembers?: boolean; | ||
mockStatic?: boolean; | ||
} | ||
@@ -122,3 +123,3 @@ | ||
*/ | ||
export interface CastMockOptions extends BaseMockOptions, CircularHandlingOptions { | ||
export interface CastMockOptions extends BaseMockOptions { | ||
/** | ||
@@ -130,9 +131,2 @@ * Maintains the prototype chain when casting objects | ||
keepPrototype?: boolean; | ||
/** | ||
* Maximum time to wait for async operations in milliseconds | ||
* Used when mocking async methods and properties | ||
* @default 5000 | ||
*/ | ||
asyncTimeout?: number; | ||
} | ||
@@ -139,0 +133,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
668
464374
7042