@corez/mock
Advanced tools
Comparing version 0.8.0 to 0.9.0
@@ -276,2 +276,6 @@ /** | ||
/** | ||
* Creates a factory-based mock class that automatically mocks instances on construction | ||
*/ | ||
factory<T extends Constructor<any>>(target: T, options?: ObjectMockOptions): T; | ||
/** | ||
* Casts a partial implementation to a complete mock | ||
@@ -278,0 +282,0 @@ */ |
@@ -1191,2 +1191,55 @@ 'use strict'; | ||
// src/mocks/factory.ts | ||
function factory(target, options = {}) { | ||
if (target[IS_MOCKED]) { | ||
return target; | ||
} | ||
const originalConstructor = target; | ||
const newConstructor = function(...args) { | ||
if (!(this instanceof newConstructor)) { | ||
return new newConstructor(...args); | ||
} | ||
const instance = Reflect.construct(originalConstructor, args, newConstructor); | ||
return obj(instance, { | ||
...options, | ||
prototypeChain: true, | ||
inPlace: true | ||
}); | ||
}; | ||
Object.setPrototypeOf(newConstructor, target); | ||
newConstructor.prototype = target.prototype; | ||
Object.defineProperty(newConstructor, IS_MOCKED, { | ||
value: true, | ||
enumerable: false, | ||
configurable: false, | ||
writable: false | ||
}); | ||
if (options.inPlace) { | ||
const mockedProto = obj(Object.create(target.prototype), { | ||
...options, | ||
inPlace: true | ||
}); | ||
Object.getOwnPropertyNames(mockedProto).forEach((prop) => { | ||
if (typeof mockedProto[prop] === "function" && prop !== "constructor") { | ||
target.prototype[prop] = mockedProto[prop]; | ||
} | ||
}); | ||
target.prototype.constructor = function(...args) { | ||
const instance = originalConstructor.apply(this, args); | ||
return obj(instance, { | ||
...options, | ||
inPlace: true | ||
}); | ||
}; | ||
Object.defineProperty(target, IS_MOCKED, { | ||
value: true, | ||
enumerable: false, | ||
configurable: false, | ||
writable: false | ||
}); | ||
return target; | ||
} | ||
return newConstructor; | ||
} | ||
// src/registry.ts | ||
@@ -1375,2 +1428,5 @@ var MockRegistry = class { | ||
} | ||
factory(target, options = {}) { | ||
return factory(target, options); | ||
} | ||
}; | ||
@@ -1388,3 +1444,4 @@ var globalMock = new MockImpl(); | ||
replace: (obj2, key, impl, options) => globalMock.replace(obj2, key, impl, options), | ||
compose: (target, _options = {}) => globalMock.compose(target, _options) | ||
compose: (target, _options = {}) => globalMock.compose(target, _options), | ||
factory: (target, options = {}) => globalMock.factory(target, options) | ||
} | ||
@@ -1391,0 +1448,0 @@ ); |
@@ -1185,2 +1185,55 @@ import rfdc from 'rfdc'; | ||
// src/mocks/factory.ts | ||
function factory(target, options = {}) { | ||
if (target[IS_MOCKED]) { | ||
return target; | ||
} | ||
const originalConstructor = target; | ||
const newConstructor = function(...args) { | ||
if (!(this instanceof newConstructor)) { | ||
return new newConstructor(...args); | ||
} | ||
const instance = Reflect.construct(originalConstructor, args, newConstructor); | ||
return obj(instance, { | ||
...options, | ||
prototypeChain: true, | ||
inPlace: true | ||
}); | ||
}; | ||
Object.setPrototypeOf(newConstructor, target); | ||
newConstructor.prototype = target.prototype; | ||
Object.defineProperty(newConstructor, IS_MOCKED, { | ||
value: true, | ||
enumerable: false, | ||
configurable: false, | ||
writable: false | ||
}); | ||
if (options.inPlace) { | ||
const mockedProto = obj(Object.create(target.prototype), { | ||
...options, | ||
inPlace: true | ||
}); | ||
Object.getOwnPropertyNames(mockedProto).forEach((prop) => { | ||
if (typeof mockedProto[prop] === "function" && prop !== "constructor") { | ||
target.prototype[prop] = mockedProto[prop]; | ||
} | ||
}); | ||
target.prototype.constructor = function(...args) { | ||
const instance = originalConstructor.apply(this, args); | ||
return obj(instance, { | ||
...options, | ||
inPlace: true | ||
}); | ||
}; | ||
Object.defineProperty(target, IS_MOCKED, { | ||
value: true, | ||
enumerable: false, | ||
configurable: false, | ||
writable: false | ||
}); | ||
return target; | ||
} | ||
return newConstructor; | ||
} | ||
// src/registry.ts | ||
@@ -1369,2 +1422,5 @@ var MockRegistry = class { | ||
} | ||
factory(target, options = {}) { | ||
return factory(target, options); | ||
} | ||
}; | ||
@@ -1382,3 +1438,4 @@ var globalMock = new MockImpl(); | ||
replace: (obj2, key, impl, options) => globalMock.replace(obj2, key, impl, options), | ||
compose: (target, _options = {}) => globalMock.compose(target, _options) | ||
compose: (target, _options = {}) => globalMock.compose(target, _options), | ||
factory: (target, options = {}) => globalMock.factory(target, options) | ||
} | ||
@@ -1385,0 +1442,0 @@ ); |
{ | ||
"name": "@corez/mock", | ||
"version": "0.8.0", | ||
"version": "0.9.0", | ||
"description": "A powerful and flexible TypeScript mocking library for testing", | ||
@@ -5,0 +5,0 @@ "keywords": [ |
118
README.md
@@ -210,20 +210,54 @@ # @corez/mock | ||
console.log(queryMock.calls.count()); | ||
``` | ||
// Or modify the original class | ||
mock.cls(Database, { | ||
inPlace: true, | ||
overrides: { | ||
query: async () => [{id: 1}], | ||
}, | ||
### Factory Mocking | ||
Create factory-based mock classes that automatically mock instances on construction: | ||
```typescript | ||
class Database { | ||
async connect() { | ||
/* ... */ | ||
} | ||
async query(sql: string) { | ||
/* ... */ | ||
} | ||
} | ||
// Create a factory mock class | ||
const MockDatabase = mock.factory(Database, { | ||
prototypeChain: true, // Mock all prototype methods | ||
}); | ||
// Store original methods for restoration | ||
const originalQuery = Database.prototype.query; | ||
const originalConnect = Database.prototype.connect; | ||
// Each instance is automatically mocked | ||
const db = new MockDatabase(); | ||
expect(db.query).toHaveProperty('mock'); // true | ||
// Later, restore original methods | ||
Database.prototype.query = originalQuery; | ||
Database.prototype.connect = originalConnect; | ||
// Mock implementation | ||
db.query.mockResolvedValue({rows: []}); | ||
// Track calls | ||
db.query('SELECT * FROM users'); | ||
expect(db.query).toHaveBeenCalled(); | ||
// In-place mocking | ||
mock.factory(Database, { | ||
inPlace: true, // Modify original class | ||
prototypeChain: true, | ||
}); | ||
// Now original class creates mocked instances | ||
const db2 = new Database(); | ||
db2.query.mockResolvedValue({rows: []}); | ||
``` | ||
Key features of factory mocking: | ||
- Automatic instance mocking on construction | ||
- Proper inheritance handling using `Reflect.construct` | ||
- Support for prototype chain mocking | ||
- In-place modification option | ||
- Automatic spy tracking for all methods | ||
- Preserves constructor behavior | ||
### Type Definitions | ||
@@ -356,2 +390,62 @@ | ||
#### `mock.factory<T extends Constructor<any>>(target: T, options?: ObjectMockOptions): T` | ||
Creates a factory-based mock class that automatically mocks instances on construction: | ||
```typescript | ||
// Create a factory mock class | ||
const MockDatabase = mock.factory(Database, { | ||
// Mock all prototype chain methods | ||
prototypeChain: true, | ||
// Modify original class instead of creating new one | ||
inPlace: false, | ||
// Override specific methods | ||
overrides: { | ||
query: async () => [{id: 1}], | ||
}, | ||
// Enable debug logging | ||
debug: false, | ||
}); | ||
// Each instance is automatically mocked | ||
const db = new MockDatabase(); | ||
// Mock implementation | ||
db.query.mockResolvedValue({rows: []}); | ||
// Track calls | ||
db.query('SELECT * FROM users'); | ||
expect(db.query).toHaveBeenCalled(); | ||
``` | ||
The factory function provides these key features: | ||
- Automatic instance mocking on construction | ||
- Proper inheritance handling using `Reflect.construct` | ||
- Support for prototype chain mocking | ||
- In-place modification option | ||
- Automatic spy tracking for all methods | ||
- Preserves constructor behavior | ||
Available options: | ||
```typescript | ||
interface FactoryOptions extends ObjectMockOptions { | ||
// When true, mocks all prototype chain methods | ||
prototypeChain?: boolean; | ||
// When true, modifies original class | ||
inPlace?: boolean; | ||
// Override specific methods | ||
overrides?: Partial<T>; | ||
// Enable debug logging | ||
debug?: boolean; | ||
} | ||
``` | ||
#### `mock.compose<T extends Fn>(): MockFunction<T>;` | ||
@@ -358,0 +452,0 @@ |
@@ -9,2 +9,3 @@ import { | ||
import {cast, cls, compose, fn, obj, replace} from './mocks'; | ||
import {factory} from './mocks/factory'; | ||
import {MockRegistry} from './registry'; | ||
@@ -106,2 +107,6 @@ import { | ||
} | ||
factory<T extends Constructor<any>>(target: T, options: ObjectMockOptions = {}): T { | ||
return factory(target, options); | ||
} | ||
} | ||
@@ -145,2 +150,4 @@ | ||
) => globalMock.compose(target, _options), | ||
factory: <T extends Constructor<any>>(target: T, options: ObjectMockOptions = {}) => | ||
globalMock.factory(target, options), | ||
}, | ||
@@ -147,0 +154,0 @@ ) as MockFn; |
@@ -414,2 +414,7 @@ import {IS_MOCKED} from '../constants/mock-symbols'; | ||
/** | ||
* Creates a factory-based mock class that automatically mocks instances on construction | ||
*/ | ||
factory<T extends Constructor<any>>(target: T, options?: ObjectMockOptions): T; | ||
/** | ||
* Casts a partial implementation to a complete mock | ||
@@ -416,0 +421,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
562136
8827
977