@corez/mock
Advanced tools
Comparing version 0.10.0 to 0.11.0
@@ -116,2 +116,9 @@ /** | ||
/** | ||
* When true, creates a new instance instead of modifying the original target | ||
* Safer than modifying the original object | ||
* @default false | ||
*/ | ||
shouldClone?: boolean; | ||
/** | ||
* @deprecated Use shouldClone instead | ||
* When true, modifies the original target instead of creating a new one | ||
@@ -153,2 +160,10 @@ * Use with caution as it affects the original definition | ||
/** | ||
* When true, ignores prototype chain methods during mocking | ||
* When false, mocks all nested objects and prototype chain methods | ||
* Only applies when shouldClone is true | ||
* @default false | ||
*/ | ||
shouldIgnorePrototype?: boolean; | ||
/** | ||
* @deprecated Use shouldIgnorePrototype instead | ||
* When true, mocks all nested objects and prototype chain methods | ||
@@ -177,2 +192,9 @@ * When false, only mocks the top level object's own methods | ||
/** | ||
* When true, replaces the original prototype chain | ||
* Important for instanceof checks and inheritance behavior | ||
* @default false | ||
*/ | ||
shouldReplacePrototype?: boolean; | ||
/** | ||
* @deprecated Use shouldReplacePrototype instead | ||
* Controls whether to preserve the original prototype chain | ||
@@ -184,2 +206,9 @@ * Important for instanceof checks and inheritance behavior | ||
/** | ||
* When true, mocks the original constructor behavior | ||
* When false, constructor calls are forwarded to the original class | ||
* @default false | ||
*/ | ||
shouldMockConstructor?: boolean; | ||
/** | ||
* @deprecated Use shouldMockConstructor instead | ||
* Controls whether to preserve the original constructor behavior | ||
@@ -201,2 +230,10 @@ * When true, constructor calls are forwarded to the original class | ||
/** | ||
* When true, ignores static members during mocking | ||
* Can be boolean to ignore all/none static members | ||
* Or array of glob patterns with ! prefix for exclusion | ||
* @default false | ||
*/ | ||
shouldIgnoreStatic?: boolean | string[]; | ||
/** | ||
* @deprecated Use shouldIgnoreStatic instead | ||
* Controls static member mocking | ||
@@ -209,2 +246,9 @@ * Can be boolean to mock all/none static members | ||
/** | ||
* When true, ignores inherited methods during mocking | ||
* When false, mocks all methods including inherited ones | ||
* @default false | ||
*/ | ||
shouldIgnoreInheritance?: boolean; | ||
/** | ||
* @deprecated Use shouldIgnoreInheritance instead | ||
* Controls whether to mock inherited methods | ||
@@ -316,7 +360,2 @@ * When false, only mocks methods defined directly on the class | ||
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 or object | ||
*/ | ||
compose<T extends Constructor<any>>(target: T, options?: ClassMockOptions<T>): ClsMock<T>; | ||
compose<T extends object>(target: T, options?: ObjectMockOptions<T>): T; | ||
} | ||
@@ -323,0 +362,0 @@ |
{ | ||
"name": "@corez/mock", | ||
"version": "0.10.0", | ||
"version": "0.11.0", | ||
"description": "A powerful and flexible TypeScript mocking library for testing", | ||
@@ -5,0 +5,0 @@ "keywords": [ |
226
README.md
@@ -63,6 +63,12 @@ # @corez/mock | ||
const userService = mock.obj<UserService>({ | ||
getUser: async id => ({id, name: 'John'}), | ||
updateUser: async user => {}, | ||
}); | ||
const userService = mock.obj<UserService>( | ||
{ | ||
getUser: async id => ({id, name: 'John'}), | ||
updateUser: async user => {}, | ||
}, | ||
{ | ||
shouldClone: true, // Create a new instance instead of modifying the original | ||
shouldIgnorePrototype: false, // Mock prototype chain methods | ||
}, | ||
); | ||
@@ -80,4 +86,5 @@ // Mock a class | ||
const MockDatabase = mock.cls(Database, { | ||
mockStatic: true, | ||
preserveConstructor: true, | ||
shouldIgnoreStatic: false, // Mock static members | ||
shouldMockConstructor: false, // Keep original constructor behavior | ||
shouldIgnoreInheritance: false, // Mock inherited methods | ||
}); | ||
@@ -138,6 +145,9 @@ | ||
Create mock classes with automatic method tracking: | ||
Create mock classes with advanced method tracking and inheritance support: | ||
```typescript | ||
class Database { | ||
static getInstance() { | ||
return new Database(); | ||
} | ||
async connect() { | ||
@@ -151,16 +161,45 @@ /* ... */ | ||
const MockDatabase = mock.cls(Database, { | ||
mockStatic: true, | ||
preserveConstructor: true, | ||
}); | ||
// Basic mocking with default options | ||
const MockDatabase = mock.cls(Database); | ||
const db = new MockDatabase(); | ||
db.query.mockResolvedValue({rows: []}); | ||
// Check if class is mocked | ||
// Advanced mocking with patterns and inheritance | ||
const AdvancedMockDatabase = mock.cls(Database, { | ||
patterns: ['getInstance'], // Only mock specific methods | ||
shouldIgnoreInheritance: false, // Mock inherited methods | ||
shouldReplacePrototype: false, // Keep original prototype chain | ||
shouldIgnoreStatic: false, // Mock static members | ||
}); | ||
// Static method mocking | ||
AdvancedMockDatabase.getInstance.mockReturnValue(new AdvancedMockDatabase()); | ||
// Pattern-based method mocking | ||
const dbWithPatterns = mock.cls(Database, { | ||
patterns: ['get*', '!getPrivate*'], // Mock all get* methods except getPrivate* | ||
shouldIgnoreStatic: false, // Enable static method mocking | ||
}); | ||
// Verify mocking | ||
console.log(MockDatabase.__is_mocked__); // true | ||
console.log(db.query.calls.count()); // Track call count | ||
console.log(db.query.calls.all()); // Get all call details | ||
// Access call information | ||
const queryMock = db.query as MockFunction; | ||
console.log(queryMock.calls.count()); | ||
// Example with all options | ||
const FullOptionsMock = mock.cls(Database, { | ||
shouldClone: true, // Create new class instead of modifying original | ||
shouldReplacePrototype: false, // Keep original prototype chain | ||
shouldMockConstructor: false, // Keep original constructor | ||
shouldIgnoreStatic: false, // Mock static members | ||
shouldIgnoreInheritance: false, // Mock inherited methods | ||
patterns: ['get*', 'set*', '!private*'], // Mock getters and setters, exclude private methods | ||
overrides: { | ||
// Custom implementations | ||
connect: async () => { | ||
/* custom connect logic */ | ||
}, | ||
query: async (sql: string) => ({rows: []}), | ||
}, | ||
}); | ||
``` | ||
@@ -174,3 +213,6 @@ | ||
const MockDatabase = mock.factory(Database, { | ||
prototypeChain: true, // Mock all prototype methods | ||
shouldIgnorePrototype: false, // Mock prototype chain methods | ||
shouldReplacePrototype: false, // Keep original prototype chain | ||
shouldMockConstructor: false, // Keep original constructor behavior | ||
patterns: ['query', 'connect'], // Only mock specific methods | ||
}); | ||
@@ -188,2 +230,19 @@ | ||
expect(db.query).toHaveBeenCalled(); | ||
// Example with all options | ||
const FullOptionsMockFactory = mock.factory(Database, { | ||
shouldClone: true, // Create new instances | ||
shouldIgnorePrototype: false, // Mock prototype methods | ||
shouldReplacePrototype: false, // Keep prototype chain | ||
shouldMockConstructor: false, // Keep constructor | ||
shouldIgnoreStatic: false, // Mock static members | ||
shouldIgnoreInheritance: false, // Mock inherited methods | ||
patterns: ['get*', 'set*', '!private*'], // Mock patterns | ||
overrides: { | ||
connect: async () => { | ||
/* custom connect */ | ||
}, | ||
query: async () => ({rows: []}), | ||
}, | ||
}); | ||
``` | ||
@@ -274,2 +333,135 @@ | ||
### Common Options | ||
Base options shared by all mock types: | ||
```typescript | ||
interface BaseMockOptions { | ||
debug?: boolean; // Enable detailed logging | ||
asyncTimeout?: number; // Timeout for async operations (ms) | ||
name?: string; // Name of mock instance for debugging | ||
} | ||
``` | ||
### Function Mock Options | ||
Options specific to function mocking: | ||
```typescript | ||
interface FunctionMockOptions extends BaseMockOptions { | ||
trackCalls?: boolean; // Track function calls | ||
autoSpy?: boolean; // Auto-create spies for function properties | ||
} | ||
``` | ||
### Object Mock Options | ||
Options specific to object mocking: | ||
```typescript | ||
interface ObjectMockOptions<T> extends BaseMockOptions { | ||
shouldClone?: boolean; // Create new instance instead of modifying original | ||
shouldIgnorePrototype?: boolean; // Ignore prototype chain methods | ||
overrides?: DeepPartial<T>; // Partial implementation to override properties | ||
patterns?: string[]; // Glob patterns for methods/properties to mock | ||
} | ||
``` | ||
### Class Mock Options | ||
Options specific to class mocking: | ||
```typescript | ||
interface ClassMockOptions<T> extends BaseMockOptions { | ||
shouldClone?: boolean; // Create new class instead of modifying original | ||
shouldReplacePrototype?: boolean; // Replace prototype chain | ||
shouldMockConstructor?: boolean; // Mock constructor behavior | ||
shouldIgnoreStatic?: boolean; // Ignore static members | ||
shouldIgnoreInheritance?: boolean; // Ignore inherited methods | ||
overrides?: DeepPartial<T>; // Partial implementation to override methods | ||
patterns?: string[]; // Glob patterns for methods to mock | ||
} | ||
``` | ||
### Migration Guide | ||
Starting from v0.11.0, we've introduced new option names that are more intuitive and consistent. Old options are still | ||
supported but marked as deprecated: | ||
| Old Option | New Option | Description | | ||
| --------------------------- | -------------------------------- | --------------------------------- | | ||
| `inplace: true` | `shouldClone: false` | Control object/class modification | | ||
| `prototypeChain: true` | `shouldIgnorePrototype: false` | Control prototype chain handling | | ||
| `preservePrototype: true` | `shouldReplacePrototype: false` | Control prototype replacement | | ||
| `preserveConstructor: true` | `shouldMockConstructor: false` | Control constructor mocking | | ||
| `static: true` | `shouldIgnoreStatic: false` | Control static member handling | | ||
| `inheritance: true` | `shouldIgnoreInheritance: false` | Control inherited method handling | | ||
Example: | ||
```typescript | ||
// Old way | ||
const mockObj = mock.obj(target, { | ||
inplace: true, | ||
prototypeChain: true, | ||
}); | ||
// New way (recommended) | ||
const mockObj = mock.obj(target, { | ||
shouldClone: false, | ||
shouldIgnorePrototype: false, | ||
}); | ||
// Class mock example | ||
const MockClass = mock.cls(TargetClass, { | ||
shouldClone: false, // Modify original class | ||
shouldReplacePrototype: false, // Keep prototype chain | ||
shouldMockConstructor: false, // Keep constructor | ||
shouldIgnoreStatic: false, // Mock static members | ||
shouldIgnoreInheritance: false, // Mock inherited methods | ||
patterns: ['get*', '!private*'], // Only mock get* methods, exclude private* | ||
}); | ||
``` | ||
### Best Practices | ||
1. **Use New Option Names** | ||
- New option names better express their purpose | ||
- Default `false` values are more intuitive | ||
- IDE provides suggestions for new options | ||
2. **Choose Appropriate Defaults** | ||
- Use `shouldClone: true` when original object needs protection | ||
- Use `shouldIgnorePrototype: true` when only own properties matter | ||
- Use `shouldMockConstructor: true` for full instance control | ||
3. **Pattern Matching** | ||
```typescript | ||
const mock = mock.cls(MyClass, { | ||
patterns: [ | ||
'get*', // Mock all getters | ||
'!private*', // Exclude private methods | ||
'handle*', // Mock all handler methods | ||
], | ||
}); | ||
``` | ||
### Pattern-based Method Mocking | ||
You can use glob patterns to control which methods get mocked: | ||
```typescript | ||
const MockDatabase = mock.cls(Database, { | ||
static: ['query*', '!queryPrivate*'], // Mock all query* methods except queryPrivate* | ||
inheritance: true, // Mock inherited methods | ||
preservePrototype: true, // Keep original implementations | ||
}); | ||
const db = new MockDatabase(); | ||
// query and queryAll will be mocked | ||
// queryPrivate and queryPrivateData will not be mocked | ||
``` | ||
### Base Options | ||
@@ -276,0 +468,0 @@ |
@@ -28,5 +28,8 @@ import {BaseMockOptions, CastMockOptions, ClassMockOptions, FunctionMockOptions, ObjectMockOptions} from './types'; | ||
...DEFAULT_BASE_OPTIONS, | ||
inplace: false, | ||
shouldClone: false, | ||
shouldIgnorePrototype: false, | ||
overrides: {}, | ||
// Backward compatibility | ||
inplace: true, | ||
prototypeChain: true, | ||
overrides: {}, | ||
}; | ||
@@ -40,3 +43,10 @@ | ||
...DEFAULT_BASE_OPTIONS, | ||
inplace: false, | ||
shouldClone: false, | ||
shouldReplacePrototype: false, | ||
shouldMockConstructor: false, | ||
shouldIgnoreStatic: false, | ||
shouldIgnoreInheritance: false, | ||
overrides: {}, | ||
// Backward compatibility | ||
inplace: true, | ||
preservePrototype: true, | ||
@@ -46,3 +56,2 @@ preserveConstructor: true, | ||
inheritance: true, | ||
overrides: {}, | ||
}; | ||
@@ -59,2 +68,17 @@ | ||
type OptionsWithBackwardCompatibility<T> = T & { | ||
inplace?: boolean; | ||
prototypeChain?: boolean; | ||
preservePrototype?: boolean; | ||
preserveConstructor?: boolean; | ||
static?: boolean | string[]; | ||
inheritance?: boolean; | ||
shouldClone?: boolean; | ||
shouldIgnorePrototype?: boolean; | ||
shouldReplacePrototype?: boolean; | ||
shouldMockConstructor?: boolean; | ||
shouldIgnoreStatic?: boolean | string[]; | ||
shouldIgnoreInheritance?: boolean; | ||
}; | ||
/** | ||
@@ -66,6 +90,30 @@ * Creates options by merging defaults with provided options | ||
export function createOptions<T extends BaseMockOptions>(defaults: T, options: Partial<T> = {}): T { | ||
return { | ||
const result = { | ||
...defaults, | ||
...options, | ||
}; | ||
} as OptionsWithBackwardCompatibility<T>; | ||
// Handle backward compatibility for object options | ||
if ('inplace' in result) { | ||
result.shouldClone = !result.inplace; | ||
} | ||
if ('prototypeChain' in result) { | ||
result.shouldIgnorePrototype = !result.prototypeChain; | ||
} | ||
// Handle backward compatibility for class options | ||
if ('preservePrototype' in result) { | ||
result.shouldReplacePrototype = !result.preservePrototype; | ||
} | ||
if ('preserveConstructor' in result) { | ||
result.shouldMockConstructor = !result.preserveConstructor; | ||
} | ||
if ('static' in result) { | ||
result.shouldIgnoreStatic = !result.static; | ||
} | ||
if ('inheritance' in result) { | ||
result.shouldIgnoreInheritance = !result.inheritance; | ||
} | ||
return result as T; | ||
} | ||
@@ -72,0 +120,0 @@ |
@@ -8,3 +8,3 @@ import { | ||
} from './defaults'; | ||
import {cast, cls, compose, fn, obj, replace} from './mocks'; | ||
import {cast, cls, fn, obj, replace} from './mocks'; | ||
import {MockRegistry} from './registry'; | ||
@@ -87,15 +87,2 @@ import { | ||
/** | ||
* Creates a mock from a class constructor or object | ||
*/ | ||
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 as T; | ||
} | ||
/** | ||
* Replaces a method with mock implementation | ||
@@ -141,6 +128,2 @@ */ | ||
globalMock.replace(obj, key, impl, options), | ||
compose: <T extends object>( | ||
target: T | Constructor<T>, | ||
_options: ObjectMockOptions<T> | ClassMockOptions<T> = {}, | ||
) => globalMock.compose(target, _options), | ||
}, | ||
@@ -147,0 +130,0 @@ ) as MockFn; |
@@ -36,3 +36,3 @@ import type {ClsMock, MockFunction} from '../../types'; | ||
it('should create a mock class that can be instantiated', () => { | ||
const MockAnimal = cls(Animal); | ||
const MockAnimal = cls(Animal, {inplace: false}); | ||
const mockAnimal = new MockAnimal('test'); | ||
@@ -44,3 +44,3 @@ expect(mockAnimal).toBeDefined(); | ||
it('should mock instance methods', () => { | ||
const MockAnimal = cls(Animal); | ||
const MockAnimal = cls(Animal, {inplace: false}); | ||
const mockAnimal = new MockAnimal('test'); | ||
@@ -52,3 +52,3 @@ expect(typeof mockAnimal.makeSound).toBe('function'); | ||
it('should allow custom implementation of methods', () => { | ||
const MockAnimal = cls(Animal); | ||
const MockAnimal = cls(Animal, {inplace: false}); | ||
const mockAnimal = new MockAnimal('test'); | ||
@@ -65,3 +65,3 @@ const mockSound = jest.fn(() => 'custom sound'); | ||
it('should handle property access and assignment', () => { | ||
const MockAnimal = cls(Animal); | ||
const MockAnimal = cls(Animal, {inplace: false}); | ||
const mockAnimal = new MockAnimal('test'); | ||
@@ -74,3 +74,3 @@ | ||
it('should track getter and setter calls', () => { | ||
const MockAnimal = cls(Animal); | ||
const MockAnimal = cls(Animal, {inplace: false}); | ||
const mockAnimal = new MockAnimal('test'); | ||
@@ -100,3 +100,3 @@ | ||
it('should allow custom implementation of getters and setters', () => { | ||
const MockAnimal = cls(Animal); | ||
const MockAnimal = cls(Animal, {inplace: false}); | ||
const mockAnimal = new MockAnimal('test'); | ||
@@ -159,3 +159,3 @@ | ||
it('should handle context correctly with regular methods', () => { | ||
const MockChildClass = cls(Child); | ||
const MockChildClass = cls(Child, {inplace: false}); | ||
const instance = new MockChildClass(); | ||
@@ -185,3 +185,3 @@ | ||
const MockBoundClass = cls(BoundChild); | ||
const MockBoundClass = cls(BoundChild, {inplace: false}); | ||
const instance = new MockBoundClass(); | ||
@@ -209,3 +209,3 @@ | ||
const MockArrowClass = cls(ArrowChild); | ||
const MockArrowClass = cls(ArrowChild, {inplace: false}); | ||
const instance = new MockArrowClass(); | ||
@@ -410,2 +410,3 @@ | ||
patterns: ['get*', '!_*'], | ||
inplace: false, | ||
}); | ||
@@ -429,2 +430,3 @@ const instance = new MockClass(); | ||
patterns: ['*', '!set*', '!_*'], | ||
inplace: false, | ||
}); | ||
@@ -445,2 +447,3 @@ const instance = new MockClass(); | ||
static: ['get*', '!_*'], | ||
inplace: false, | ||
}); | ||
@@ -462,2 +465,3 @@ | ||
static: true, | ||
inplace: false, | ||
}); | ||
@@ -474,2 +478,3 @@ | ||
static: false, | ||
inplace: false, | ||
}); | ||
@@ -498,2 +503,3 @@ | ||
inheritance: false, | ||
inplace: false, | ||
}); | ||
@@ -519,2 +525,3 @@ const instance = new MockClass(); | ||
inheritance: false, | ||
inplace: false, | ||
}); | ||
@@ -539,3 +546,3 @@ | ||
it('should track method calls', () => { | ||
const MockAnimal = cls(Animal); | ||
const MockAnimal = cls(Animal, {inplace: false}); | ||
const mockAnimal = new MockAnimal('test'); | ||
@@ -550,3 +557,3 @@ const mockSound = jest.fn(() => 'test sound'); | ||
it('should track method calls with arguments', () => { | ||
const MockAnimal = cls(Animal); | ||
const MockAnimal = cls(Animal, {inplace: false}); | ||
const mockAnimal = new MockAnimal('test'); | ||
@@ -615,3 +622,3 @@ const setterMock = jest.fn(); | ||
it('should mark mock class with IS_MOCKED symbol', () => { | ||
const MockClass = cls(TestClass); | ||
const MockClass = cls(TestClass, {inplace: false}); | ||
expect((MockClass as ClsMock<typeof TestClass>).__is_mocked__).toBe(true); | ||
@@ -626,4 +633,4 @@ }); | ||
it('should not create mock if class is already mocked', () => { | ||
const FirstMock = cls(TestClass); | ||
const SecondMock = cls(FirstMock); | ||
const FirstMock = cls(TestClass, {inplace: false}); | ||
const SecondMock = cls(FirstMock, {inplace: false}); | ||
expect(SecondMock).toBe(FirstMock); | ||
@@ -638,3 +645,3 @@ }); | ||
} | ||
const MockChildClass = cls(ChildClass); | ||
const MockChildClass = cls(ChildClass, {inplace: false}); | ||
expect((MockChildClass as ClsMock<typeof ChildClass>).__is_mocked__).toBe(true); | ||
@@ -641,0 +648,0 @@ }); |
@@ -22,3 +22,3 @@ import {IS_MOCKED} from '../../constants/mock-symbols'; | ||
it('should create mock from object', () => { | ||
const mock = obj(testObject); | ||
const mock = obj(testObject, {inplace: false}); | ||
expect(mock).toBeDefined(); | ||
@@ -30,3 +30,3 @@ expect(mock.name).toBe('test'); | ||
it('should track method calls', () => { | ||
const mock = obj(testObject); | ||
const mock = obj(testObject, {inplace: false}); | ||
mock.getValue(); | ||
@@ -38,2 +38,3 @@ expect(mock.getValue).toHaveBeenCalled(); | ||
const mock = obj(testObject, { | ||
inplace: false, | ||
overrides: { | ||
@@ -72,3 +73,3 @@ getValue: () => 456, | ||
const _original = new TestClass('test'); | ||
const mock = obj(_original); | ||
const mock = obj(_original, {inplace: false}); | ||
expect(mock).not.toBe(_original); | ||
@@ -114,3 +115,3 @@ }); | ||
const child = new Child(); | ||
const mock = obj(child); | ||
const mock = obj(child, {inplace: false}); | ||
expect(mock.getValue()).toBe('child:parent'); | ||
@@ -139,3 +140,3 @@ expect(mock.childMethod()).toBe('child:base'); | ||
const child = new Child(); | ||
const mock = obj(child, {prototypeChain: false}); | ||
const mock = obj(child, {inplace: false, prototypeChain: false}); | ||
expect(mock.getValue()).toBe('child:parent'); | ||
@@ -157,3 +158,3 @@ expect(mock.childMethod()).toBe('child:base'); | ||
const mock = obj(_original, {prototypeChain: true}); | ||
const mock = obj(_original, {inplace: false, prototypeChain: true}); | ||
expect(mock).not.toBe(_original); | ||
@@ -180,3 +181,3 @@ expect(mock.nested).not.toBe(_original.nested); | ||
const mock = obj(circular, {prototypeChain: true}); | ||
const mock = obj(circular, {inplace: false, prototypeChain: true}); | ||
expect(mock).not.toBe(circular); | ||
@@ -245,3 +246,3 @@ expect(mock.self).toBe(mock); | ||
it('should handle context correctly with mock', () => { | ||
const mockedInstance = obj(instance); | ||
const mockedInstance = obj(instance, {inplace: false}); | ||
@@ -257,3 +258,3 @@ mockedInstance.setName('test'); | ||
const mockedInstance = obj(boundInstance); | ||
const mockedInstance = obj(boundInstance, {inplace: false}); | ||
@@ -265,3 +266,3 @@ mockedInstance.setName('test'); | ||
it('should work using call', () => { | ||
const mockedInstance = obj(instance); | ||
const mockedInstance = obj(instance, {inplace: false}); | ||
@@ -283,3 +284,3 @@ mockedInstance.setName.call(instance, 'test'); | ||
const testObj = {method: () => 'test'}; | ||
const mock = obj(testObj); | ||
const mock = obj(testObj, {inplace: false}); | ||
expect(IS_MOCKED in mock).toBe(true); | ||
@@ -291,4 +292,4 @@ expect(isMocked(mock)).toBe(true); | ||
const testObj = {method: () => 'test'}; | ||
const firstMock = obj(testObj); | ||
const secondMock = obj(firstMock); | ||
const firstMock = obj(testObj, {inplace: false}); | ||
const secondMock = obj(firstMock, {inplace: false}); | ||
expect(secondMock).toBe(firstMock); | ||
@@ -357,2 +358,3 @@ }); | ||
const mock = obj(service, { | ||
inplace: false, | ||
patterns: ['get*'], | ||
@@ -371,2 +373,3 @@ prototypeChain: true, | ||
const mock = obj(service, { | ||
inplace: false, | ||
patterns: ['*', '!set*', '!_*'], | ||
@@ -386,2 +389,3 @@ prototypeChain: true, | ||
const mock = obj(service, { | ||
inplace: false, | ||
patterns: ['get*', '!_*'], | ||
@@ -388,0 +392,0 @@ prototypeChain: true, |
// Export all mock-related functions | ||
export * from './cast'; | ||
export * from './class'; | ||
export * from './compose'; | ||
export * from './function'; | ||
export * from './object'; | ||
export * from './replace'; |
@@ -111,2 +111,10 @@ import {IS_MOCKED} from '../constants/mock-symbols'; | ||
/** | ||
* When true, creates a new instance instead of modifying the original target | ||
* Safer than modifying the original object | ||
* @default false | ||
*/ | ||
shouldClone?: boolean; | ||
/** | ||
* @deprecated Use shouldClone instead | ||
* When true, modifies the original target instead of creating a new one | ||
@@ -152,2 +160,11 @@ * Use with caution as it affects the original definition | ||
/** | ||
* When true, ignores prototype chain methods during mocking | ||
* When false, mocks all nested objects and prototype chain methods | ||
* Only applies when shouldClone is true | ||
* @default false | ||
*/ | ||
shouldIgnorePrototype?: boolean; | ||
/** | ||
* @deprecated Use shouldIgnorePrototype instead | ||
* When true, mocks all nested objects and prototype chain methods | ||
@@ -178,2 +195,10 @@ * When false, only mocks the top level object's own methods | ||
/** | ||
* When true, replaces the original prototype chain | ||
* Important for instanceof checks and inheritance behavior | ||
* @default false | ||
*/ | ||
shouldReplacePrototype?: boolean; | ||
/** | ||
* @deprecated Use shouldReplacePrototype instead | ||
* Controls whether to preserve the original prototype chain | ||
@@ -186,2 +211,10 @@ * Important for instanceof checks and inheritance behavior | ||
/** | ||
* When true, mocks the original constructor behavior | ||
* When false, constructor calls are forwarded to the original class | ||
* @default false | ||
*/ | ||
shouldMockConstructor?: boolean; | ||
/** | ||
* @deprecated Use shouldMockConstructor instead | ||
* Controls whether to preserve the original constructor behavior | ||
@@ -205,2 +238,11 @@ * When true, constructor calls are forwarded to the original class | ||
/** | ||
* When true, ignores static members during mocking | ||
* Can be boolean to ignore all/none static members | ||
* Or array of glob patterns with ! prefix for exclusion | ||
* @default false | ||
*/ | ||
shouldIgnoreStatic?: boolean | string[]; | ||
/** | ||
* @deprecated Use shouldIgnoreStatic instead | ||
* Controls static member mocking | ||
@@ -214,2 +256,10 @@ * Can be boolean to mock all/none static members | ||
/** | ||
* When true, ignores inherited methods during mocking | ||
* When false, mocks all methods including inherited ones | ||
* @default false | ||
*/ | ||
shouldIgnoreInheritance?: boolean; | ||
/** | ||
* @deprecated Use shouldIgnoreInheritance instead | ||
* Controls whether to mock inherited methods | ||
@@ -458,20 +508,2 @@ * When false, only mocks methods defined directly on the class | ||
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 or object | ||
*/ | ||
compose<T extends Constructor<any>>(target: T, options?: ClassMockOptions<T>): ClsMock<T>; | ||
compose<T extends object>(target: T, options?: ObjectMockOptions<T>): T; | ||
} | ||
/** | ||
* Unified options type for compose function | ||
*/ | ||
export type ComposeOptions<T> = BaseMockOptions & | ||
(T extends new (...args: any[]) => any | ||
? ClassMockOptions<InstanceType<T>> | ||
: ObjectMockOptions<T> & { | ||
replace?: { | ||
[K in keyof T]?: T[K] extends Fn ? Fn : never; | ||
}; | ||
}); |
@@ -54,16 +54,34 @@ import {isMatch} from 'micromatch'; | ||
let currentProto = Object.getPrototypeOf(originalClass); | ||
let currentProto = originalClass; | ||
while (currentProto && currentProto !== Function.prototype) { | ||
Object.getOwnPropertyNames(currentProto) | ||
const propertyNames = Object.getOwnPropertyNames(currentProto); | ||
const propertyDescriptors = Object.getOwnPropertyDescriptors(currentProto); | ||
propertyNames | ||
.filter(prop => { | ||
if (typeof currentProto[prop] !== 'function') return false; | ||
// First check if the property name matches the pattern | ||
if (Array.isArray(config.static)) { | ||
return shouldMockMember(prop, config.static); | ||
if (!shouldMockMember(prop, config.static)) { | ||
return false; | ||
} | ||
} | ||
return true; | ||
// Check if it's a function or getter without accessing the property | ||
const descriptor = propertyDescriptors[prop]; | ||
return typeof descriptor.value === 'function' || typeof descriptor.get === 'function'; | ||
}) | ||
.forEach(methodName => { | ||
if (!mockClass[methodName]) { | ||
const originalMethod = currentProto[methodName].bind(mockClass); | ||
const descriptor = propertyDescriptors[methodName]; | ||
let originalMethod; | ||
if (descriptor.get) { | ||
// Handle getter | ||
originalMethod = descriptor.get.call(currentProto); | ||
} else { | ||
// Handle regular method | ||
originalMethod = descriptor.value.bind(mockClass); | ||
} | ||
const spy = createSpy(config.preservePrototype ? originalMethod : undefined); | ||
@@ -78,2 +96,3 @@ Object.defineProperty(mockClass, methodName, { | ||
}); | ||
if (!config.inheritance) break; | ||
@@ -80,0 +99,0 @@ currentProto = Object.getPrototypeOf(currentProto); |
Sorry, the diff of this file is too big to display
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 too big to display
Sorry, the diff of this file is not supported yet
1190366
14879
661
62