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

目录
Features
- 🎯 Type Safety - Full TypeScript support with precise type inference
- 🔄 Deep Mocking - Automatic mocking of nested objects and methods
- 🕵️ Spy Tracking - Comprehensive call tracking and verification
- 🎭 Multiple Mocking Styles - Support for functions, objects, and classes
- 🔗 Inheritance Support - Proper handling of class inheritance and prototype chains
- 🎮 Intuitive API - Clean and chainable API design
- 🛡 Debug Support - Detailed logging for troubleshooting
- 🔄 In-Place Mocking - Option to modify original classes with restore capability
Installation
npm install @corez/mock --save-dev
yarn add -D @corez/mock
pnpm add -D @corez/mock
Quick Start
import {mock} from '@corez/mock';
const greet = mock.fn<(name: string) => string>();
greet.mockImplementation(name => `Hello, ${name}!`);
interface UserService {
getUser(id: number): Promise<User>;
updateUser(user: User): Promise<void>;
}
const userService = mock.obj<UserService>(
{
getUser: async id => ({id, name: 'John'}),
updateUser: async user => {},
},
{
shouldClone: true,
shouldIgnorePrototype: false,
},
);
class Database {
async connect() {
}
async query(sql: string) {
}
}
const MockDatabase = mock.cls(Database, {
shouldIgnoreStatic: false,
shouldMockConstructor: false,
shouldIgnoreInheritance: false,
});
const db = new MockDatabase();
db.query.mockResolvedValue({rows: []});
Core APIs
mock.fn() - Function Mocking
Create standalone mock functions with full tracking capabilities:
const mockFn = mock.fn<(x: number) => number>();
mockFn.mockImplementation(x => x * 2);
mockFn.mockReturnValue(42);
mockFn.mockResolvedValue('result');
mockFn.mockRejectedValue(new Error('failed'));
expect(mockFn.calls.count()).toBe(1);
expect(mockFn.calls.all()[0].args).toEqual([1]);
mock.obj() - Object Mocking
Create mock objects with automatic method tracking:
interface UserService {
getUser(id: number): Promise<User>;
updateUser(user: User): Promise<void>;
}
const userService = mock.obj<UserService>({
getUser: async id => ({id, name: 'John'}),
updateUser: async user => {},
});
userService.getUser(1);
expect((userService.getUser as any).mock.calls.length).toBe(1);
(userService.getUser as any).mockResolvedValue({id: 1, name: 'Mock'});
mock.cls() - Class Mocking
Create mock classes with advanced method tracking and inheritance support:
class Database {
static getInstance() {
return new Database();
}
async connect() {
}
async query(sql: string) {
}
}
const MockDatabase = mock.cls(Database);
const db = new MockDatabase();
db.query.mockResolvedValue({rows: []});
const AdvancedMockDatabase = mock.cls(Database, {
patterns: ['getInstance'],
shouldIgnoreInheritance: false,
shouldReplacePrototype: false,
shouldIgnoreStatic: false,
});
AdvancedMockDatabase.getInstance.mockReturnValue(new AdvancedMockDatabase());
const dbWithPatterns = mock.cls(Database, {
patterns: ['get*', '!getPrivate*'],
shouldIgnoreStatic: false,
});
console.log(MockDatabase.__is_mocked__);
console.log(db.query.calls.count());
console.log(db.query.calls.all());
const FullOptionsMock = mock.cls(Database, {
shouldClone: true,
shouldReplacePrototype: false,
shouldMockConstructor: false,
shouldIgnoreStatic: false,
shouldIgnoreInheritance: false,
patterns: ['get*', 'set*', '!private*'],
overrides: {
connect: async () => {
},
query: async (sql: string) => ({rows: []}),
},
});
mock.factory() - Factory Mocking
Create factory-based mock classes that automatically mock instances:
const MockDatabase = mock.factory(Database, {
shouldIgnorePrototype: false,
shouldReplacePrototype: false,
shouldMockConstructor: false,
patterns: ['query', 'connect'],
});
const db = new MockDatabase();
expect(db.query).toHaveProperty('mock');
db.query.mockResolvedValue({rows: []});
db.query('SELECT * FROM users');
expect(db.query).toHaveBeenCalled();
const FullOptionsMockFactory = mock.factory(Database, {
shouldClone: true,
shouldIgnorePrototype: false,
shouldReplacePrototype: false,
shouldMockConstructor: false,
shouldIgnoreStatic: false,
shouldIgnoreInheritance: false,
patterns: ['get*', 'set*', '!private*'],
overrides: {
connect: async () => {
},
query: async () => ({rows: []}),
},
});
mock.compose() - Composition Mocking
Create mocks from classes, objects, or functions:
const mockFn = mock.compose<(x: number) => string>();
mockFn.mockImplementation(x => x.toString());
const MockDatabase = mock.compose(Database, {
overrides: {
query: async () => [{id: 1}],
},
});
const api = mock.compose<Api>(
{
fetch: async url => ({data: []}),
},
{
overrides: {
fetch: async url => ({data: [{id: 1}]}),
},
},
);
mock.cast() - Type Casting
Cast partial implementations to complete mocks:
interface CompleteApi {
getUsers(): Promise<User[]>;
getUser(id: number): Promise<User>;
createUser(user: User): Promise<void>;
updateUser(user: User): Promise<void>;
deleteUser(id: number): Promise<void>;
}
const api = mock.cast<CompleteApi>({
getUsers: async () => [{id: 1, name: 'John'}],
getUser: async id => ({id, name: 'John'}),
});
await api.createUser({id: 1, name: 'Test'});
mock.replace() - Method Replacement
Replace methods while preserving original implementation:
class Service {
async getData() {
}
async processData(data: any) {
}
}
const service = new Service();
mock.replace(service, 'getData', async () => ['mocked']);
expect(await service.getData()).toEqual(['mocked']);
expect(service.processData).toBeDefined();
mock.restore(service);
Mock Options
Common Options
Base options shared by all mock types:
interface BaseMockOptions {
debug?: boolean;
asyncTimeout?: number;
name?: string;
}
Function Mock Options
Options specific to function mocking:
interface FunctionMockOptions extends BaseMockOptions {
trackCalls?: boolean;
autoSpy?: boolean;
}
Object Mock Options
Options specific to object mocking:
interface ObjectMockOptions<T> extends BaseMockOptions {
shouldClone?: boolean;
shouldIgnorePrototype?: boolean;
overrides?: DeepPartial<T>;
patterns?: string[];
}
Class Mock Options
Options specific to class mocking:
interface ClassMockOptions<T> extends BaseMockOptions {
shouldClone?: boolean;
shouldReplacePrototype?: boolean;
shouldMockConstructor?: boolean;
shouldIgnoreStatic?: boolean;
shouldIgnoreInheritance?: boolean;
overrides?: DeepPartial<T>;
patterns?: string[];
}
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:
const mockObj = mock.obj(target, {
inplace: true,
prototypeChain: true,
});
const mockObj = mock.obj(target, {
shouldClone: false,
shouldIgnorePrototype: false,
});
const MockClass = mock.cls(TargetClass, {
shouldClone: false,
shouldReplacePrototype: false,
shouldMockConstructor: false,
shouldIgnoreStatic: false,
shouldIgnoreInheritance: false,
patterns: ['get*', '!private*'],
});
Best Practices
-
Use New Option Names
- New option names better express their purpose
- Default
false
values are more intuitive - IDE provides suggestions for new options
-
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
-
Pattern Matching
const mock = mock.cls(MyClass, {
patterns: [
'get*',
'!private*',
'handle*',
],
});
Pattern-based Method Mocking
You can use glob patterns to control which methods get mocked:
const MockDatabase = mock.cls(Database, {
static: ['query*', '!queryPrivate*'],
inheritance: true,
preservePrototype: true,
});
const db = new MockDatabase();
Base Options
All mock types support these basic options:
debug
: Enable detailed logging (default: false)asyncTimeout
: Maximum time for async operations (default: 5000ms)
Function Mock Options
trackCalls
: Record arguments and return values (default: false)autoSpy
: Create spies for function properties (default: false)
Object Mock Options
inplace
: Modify original object or create new one (default: false)prototypeChain
: Deep clone and mock prototype chain (default: true)overrides
: Override specific properties or methods
Class Mock Options
inplace
: Modify original class or create new one (default: false)preservePrototype
: Preserve prototype chain (default: true)preserveConstructor
: Preserve constructor behavior (default: true)mockStatic
: Mock static members (default: false)overrides
: Override specific methods
Advanced Usage
Pattern Matching
Control which methods to mock using glob patterns:
const service = mock.obj<Service>(
{
getData: () => Promise.resolve({data: 'test'}),
getDataAsync: () => Promise.resolve({data: 'test'}),
setData: data => {},
setDataAsync: async data => {},
},
{
patterns: [
'get*',
'!setData',
],
},
);
Async Mocking
Handle async operations:
const api = mock.obj<Api>(
{},
{
overrides: {
fetch: async url => {
if (url === '/users') {
return [{id: 1}];
}
throw new Error('Not found');
},
},
},
);
Partial Mocking
Selectively mock methods:
class UserService {
async getUser(id: number) {
}
async validate(user: User) {
}
}
const service = new UserService();
mock.replace(service, 'getUser', async id => ({
id,
name: 'Mock User',
}));
const partialService = mock.obj<UserService>(
{},
{
overrides: {
getUser: async id => ({id, name: 'Mock User'}),
},
},
);
Debugging
Debug Logging
Enable detailed logging for troubleshooting:
import {debug} from '@corez/mock';
debug.enable();
debug.setLevel('error');
debug.setHandler((level, message) => {
console.log(`[MOCK] ${level}: ${message}`);
});
Call Tracking
Track and verify method calls:
mock.mockClear();
mock.mockReset();
mock.mockRestore();
mock.calls.all();
mock.calls.count();
Best Practices
-
Type Safety
- Always provide type parameters
- Use interfaces for mock shapes
- Let TypeScript infer return types
-
Pattern Matching
- Use specific patterns for needed methods
- Combine positive and negative patterns
- Group related methods with patterns
-
Performance
- Disable prototype chain mocking when not needed
- Use shallow cloning for simple objects
- Clean up mocks after use
-
Error Handling
- Test error cases
- Verify error messages
- Handle async errors properly
Troubleshooting
Common Issues
-
Mock Not Tracking Calls
- Enable
trackCalls
for functions - Method calls tracked automatically for objects/classes
- Check mock property access
-
Type Inference Issues
- Use explicit type parameters
- Define complete interfaces
- Use
as MockFunction<T>
for better typing
-
Prototype Chain Issues
- Use
preservePrototype: true
for classes - Use
prototypeChain: true
for objects - Use
keepPrototype: true
for casting
-
Circular References
- Use
WeakSet
to track processed objects - Enable debug logging
- Consider shallow cloning
Contributing
We welcome contributions! Please see our Contributing Guide for details.
License
Apache-2.0 - see LICENSE for details.