🚀 Big News: Socket Acquires Coana to Bring Reachability Analysis to Every Appsec Team.Learn more
Socket
Book a DemoInstallSign in
Socket

@corez/mock

Package Overview
Dependencies
Maintainers
0
Versions
24
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@corez/mock - npm Package Compare versions

Comparing version

to
0.5.0

src/utils/__tests__/debug.spec.ts

64

dist/cjs/index.d.ts

@@ -26,2 +26,54 @@ /**

/**
* Log level for controlling debug output verbosity
*/
declare enum LogLevel {
NONE = 0,
ERROR = 1,
WARN = 2,
INFO = 3,
DEBUG = 4,
TRACE = 5
}
/**
* Logging configuration options
*/
interface LoggingOptions {
/**
* Whether to enable detailed logging
* @default false
*/
debug?: boolean;
/**
* Log level for controlling output verbosity
* If debug is true, defaults to LogLevel.DEBUG
* If debug is false, defaults to LogLevel.INFO
* @default LogLevel.INFO
*/
level?: LogLevel;
/**
* Custom prefix for log messages
* If not provided, uses the mock name if available
*/
prefix?: string;
/**
* Whether to output logs to console
* Controls if logs should be written to console or suppressed
* @default true if debug is true, false otherwise
*/
consoleOutput?: boolean;
/**
* Custom log formatter
* @param message The log message
* @param level Current log level
* @param context Additional context data
*/
formatter?: (message: string, level: LogLevel, context?: Record<string, any>) => string;
/**
* Custom logger implementation
* @param message Formatted log message
* @param level Current log level
*/
logger?: (message: string, level: LogLevel) => void;
}
/**
* Base configuration options for all mock types

@@ -33,3 +85,3 @@ * These options control general mock behavior

* Enables detailed logging of mock operations
* Useful for debugging and understanding mock behavior
* Shorthand for logging.debug = true
* @default false

@@ -39,2 +91,7 @@ */

/**
* Logging configuration
* If debug is true, this will be merged with {debug: true, level: LogLevel.DEBUG}
*/
logging?: LoggingOptions;
/**
* Maximum time to wait for async operations in milliseconds

@@ -45,2 +102,7 @@ * Used when mocking async methods and properties

asyncTimeout?: number;
/**
* Name for the mock instance
* Used in debug logs to identify the mock
*/
name?: string;
}

@@ -47,0 +109,0 @@ /**

@@ -292,2 +292,82 @@ 'use strict';

// src/utils/debug.ts
var DebugContext = class {
constructor(options = {}) {
this.options = {
debug: false,
level: 3 /* INFO */,
logToConsole: true,
...options
};
this.mockName = options.name || "anonymous-mock";
}
/**
* Log a debug message
*/
log(message, level = 3 /* INFO */) {
if (!this.options.debug || level > (this.options.level || 3 /* INFO */)) {
return;
}
const prefix = this.options.logPrefix || `[${this.mockName}]`;
const formattedMessage = `${prefix} ${message}`;
if (this.options.customLogger) {
this.options.customLogger(formattedMessage, level);
return;
}
if (this.options.logToConsole) {
switch (level) {
case 1 /* ERROR */:
console.error(formattedMessage);
break;
case 2 /* WARN */:
console.warn(formattedMessage);
break;
case 3 /* INFO */:
console.info(formattedMessage);
break;
case 4 /* DEBUG */:
case 5 /* TRACE */:
console.debug(formattedMessage);
break;
}
}
}
/**
* Log method call
*/
logMethodCall(methodName, args) {
this.log(`Method ${methodName} called with args: ${JSON.stringify(args)}`, 4 /* DEBUG */);
}
/**
* Log method return
*/
logMethodReturn(methodName, result) {
this.log(`Method ${methodName} returned: ${JSON.stringify(result)}`, 4 /* DEBUG */);
}
/**
* Log mock creation
*/
logMockCreation(type) {
this.log(`Created new ${type} mock`, 3 /* INFO */);
}
/**
* Log mock configuration
*/
logMockConfig(config) {
this.log(`Configured with options: ${JSON.stringify(config)}`, 4 /* DEBUG */);
}
/**
* Log error
*/
logError(error) {
this.log(`Error: ${error.message}`, 1 /* ERROR */);
if (error.stack) {
this.log(`Stack: ${error.stack}`, 4 /* DEBUG */);
}
}
};
function createDebugContext(options) {
return new DebugContext(options);
}
// src/utils/performance.ts

@@ -325,5 +405,12 @@ function calculateStats(durations) {

var MethodTracker = class {
constructor() {
constructor(target, options = {}) {
this.calls = [];
this.expectations = [];
this.target = target || {};
if (options.debug) {
this.debugContext = new DebugContext({
name: options.name || (target && target.constructor ? target.constructor.name : "anonymous"),
...options
});
}
}

@@ -467,2 +554,11 @@ /**

}
trackMethodCall(methodName, args) {
this.debugContext?.logMethodCall(methodName, args);
}
trackMethodReturn(methodName, result) {
this.debugContext?.logMethodReturn(methodName, result);
}
trackError(methodName, error) {
this.debugContext?.logError(error);
}
};

@@ -629,14 +725,30 @@

// src/utils/method-spy.ts
function createMethodSpy(method, context, options = {}) {
({
...DEFAULT_FUNCTION_OPTIONS,
...options
function createMethodSpy(target, methodName, options = {}, originalMethod) {
const tracker = new MethodTracker(target, options);
const method = originalMethod || target[methodName];
const spy = createSpy();
if (!options.preservePrototype) {
return spy;
}
spy.mockImplementation(function(...args) {
tracker.trackMethodCall(methodName, args);
try {
const result = method.apply(this, args);
if (result instanceof Promise) {
return result.then((asyncResult) => {
tracker.trackMethodReturn(methodName, asyncResult);
return asyncResult;
}).catch((error) => {
tracker.trackError(methodName, error);
throw error;
});
}
tracker.trackMethodReturn(methodName, result);
return result;
} catch (error) {
tracker.trackError(methodName, error);
throw error;
}
});
const spy = new UniversalSpy();
const spyFn = spy.getSpy();
spyFn.mockImplementation(function(...args) {
const result = method.apply(context || this, args);
return result;
});
return spyFn;
return spy;
}

@@ -651,3 +763,3 @@

const method = currentProto[methodName].bind(mockClass);
mockClass[methodName] = createMethodSpy(method, mockClass, options);
mockClass[methodName] = createMethodSpy(mockClass, methodName, options, method);
}

@@ -658,2 +770,13 @@ });

}
function createPropertyDescriptor(target, name, descriptor, config) {
const getterSpy = descriptor.get ? createMethodSpy(target, name, config, descriptor.get) : void 0;
const setterSpy = descriptor.set ? createMethodSpy(target, name, config, descriptor.set) : void 0;
return {
...descriptor,
get: getterSpy,
set: setterSpy,
configurable: true,
enumerable: descriptor.enumerable
};
}
function cls(target, options = {}) {

@@ -682,3 +805,3 @@ const config = {

...descriptor,
value: createMethodSpy(override, context, config)
value: createMethodSpy(context, name, config, override)
};

@@ -688,3 +811,3 @@ }

...descriptor,
value: config.preservePrototype ? createMethodSpy(value, context, config) : createSpy()
value: config.preservePrototype ? createMethodSpy(context, name, config, value) : createSpy()
};

@@ -695,4 +818,4 @@ }

const setterOverride = typeof name === "string" && config.overrides?.[`set${name.charAt(0).toUpperCase()}${name.slice(1)}`];
const getterSpy = get && (getterOverride ? createMethodSpy(getterOverride, context, config) : config.preservePrototype ? createMethodSpy(get, context, config) : createSpy());
const setterSpy = set && (setterOverride ? createMethodSpy(setterOverride, context, config) : config.preservePrototype ? createMethodSpy(set, context, config) : createSpy());
const getterSpy = get && (getterOverride ? createMethodSpy(context, name, config, getterOverride) : config.preservePrototype ? createMethodSpy(context, name, config, get) : createSpy());
const setterSpy = set && (setterOverride ? createMethodSpy(context, name, config, setterOverride) : config.preservePrototype ? createMethodSpy(context, name, config, set) : createSpy());
return {

@@ -710,11 +833,7 @@ ...descriptor,

const descriptor = Object.getOwnPropertyDescriptor(source, name);
const newDescriptor = handleDescriptor(name, descriptor, target2);
if (!config.mockInPlace || !target2.hasOwnProperty(name)) {
Object.defineProperty(target2, name, newDescriptor);
} else {
if (typeof source[name] === "function") {
target2[name] = createMethodSpy(source[name], target2, config);
} else {
Object.defineProperty(target2, name, newDescriptor);
}
if (typeof source[name] === "function") {
const override = config.overrides?.[name];
target2[name] = createMethodSpy(target2, name, config, override || source[name]);
} else if (descriptor) {
Object.defineProperty(target2, name, createPropertyDescriptor(target2, name, descriptor, config));
}

@@ -729,5 +848,5 @@ });

if (typeof proto[name] === "function") {
proto[name] = createMethodSpy(proto[name], proto, config);
proto[name] = createMethodSpy(proto, name, config, proto[name]);
} else if (descriptor) {
Object.defineProperty(proto, name, handleDescriptor(name, descriptor, proto));
Object.defineProperty(proto, name, createPropertyDescriptor(proto, name, descriptor, config));
}

@@ -738,7 +857,7 @@ });

if (typeof target[name] === "function") {
target[name] = createMethodSpy(target[name], target, config);
target[name] = createMethodSpy(target, name, config, target[name]);
} else {
const descriptor = Object.getOwnPropertyDescriptor(target, name);
if (descriptor) {
Object.defineProperty(target, name, handleDescriptor(name, descriptor, target));
Object.defineProperty(target, name, createPropertyDescriptor(target, name, descriptor, config));
}

@@ -970,4 +1089,5 @@ }

const result = obj(target, config);
if (options.replace) {
Object.entries(options.replace).forEach(([key, impl]) => {
const objOptions = options;
if (objOptions.replace) {
Object.entries(objOptions.replace).forEach(([key, impl]) => {
if (impl && typeof impl === "function") {

@@ -1105,2 +1225,3 @@ replace(result, key, impl);

this.currentOptions = { ...DEFAULT_BASE_OPTIONS, ...options };
this.debugContext = createDebugContext(options);
}

@@ -1111,2 +1232,4 @@ /**

fn(_options) {
this.debugContext.logMockCreation("function");
this.debugContext.logMockConfig(_options);
const mockFn = fn();

@@ -1120,2 +1243,4 @@ this.registerMock(mockFn, "function");

obj(target, options = {}) {
this.debugContext.logMockCreation("object");
this.debugContext.logMockConfig(options);
const config = createObjectOptions({ ...this.currentOptions, ...options });

@@ -1130,2 +1255,4 @@ const mockObj = obj(target, config);

cls(target, options) {
this.debugContext.logMockCreation("class");
this.debugContext.logMockConfig(options);
const config = createClassOptions({ ...this.currentOptions, ...options });

@@ -1132,0 +1259,0 @@ const mockCls = cls(target, config);

@@ -286,2 +286,82 @@ import rfdc from 'rfdc';

// src/utils/debug.ts
var DebugContext = class {
constructor(options = {}) {
this.options = {
debug: false,
level: 3 /* INFO */,
logToConsole: true,
...options
};
this.mockName = options.name || "anonymous-mock";
}
/**
* Log a debug message
*/
log(message, level = 3 /* INFO */) {
if (!this.options.debug || level > (this.options.level || 3 /* INFO */)) {
return;
}
const prefix = this.options.logPrefix || `[${this.mockName}]`;
const formattedMessage = `${prefix} ${message}`;
if (this.options.customLogger) {
this.options.customLogger(formattedMessage, level);
return;
}
if (this.options.logToConsole) {
switch (level) {
case 1 /* ERROR */:
console.error(formattedMessage);
break;
case 2 /* WARN */:
console.warn(formattedMessage);
break;
case 3 /* INFO */:
console.info(formattedMessage);
break;
case 4 /* DEBUG */:
case 5 /* TRACE */:
console.debug(formattedMessage);
break;
}
}
}
/**
* Log method call
*/
logMethodCall(methodName, args) {
this.log(`Method ${methodName} called with args: ${JSON.stringify(args)}`, 4 /* DEBUG */);
}
/**
* Log method return
*/
logMethodReturn(methodName, result) {
this.log(`Method ${methodName} returned: ${JSON.stringify(result)}`, 4 /* DEBUG */);
}
/**
* Log mock creation
*/
logMockCreation(type) {
this.log(`Created new ${type} mock`, 3 /* INFO */);
}
/**
* Log mock configuration
*/
logMockConfig(config) {
this.log(`Configured with options: ${JSON.stringify(config)}`, 4 /* DEBUG */);
}
/**
* Log error
*/
logError(error) {
this.log(`Error: ${error.message}`, 1 /* ERROR */);
if (error.stack) {
this.log(`Stack: ${error.stack}`, 4 /* DEBUG */);
}
}
};
function createDebugContext(options) {
return new DebugContext(options);
}
// src/utils/performance.ts

@@ -319,5 +399,12 @@ function calculateStats(durations) {

var MethodTracker = class {
constructor() {
constructor(target, options = {}) {
this.calls = [];
this.expectations = [];
this.target = target || {};
if (options.debug) {
this.debugContext = new DebugContext({
name: options.name || (target && target.constructor ? target.constructor.name : "anonymous"),
...options
});
}
}

@@ -461,2 +548,11 @@ /**

}
trackMethodCall(methodName, args) {
this.debugContext?.logMethodCall(methodName, args);
}
trackMethodReturn(methodName, result) {
this.debugContext?.logMethodReturn(methodName, result);
}
trackError(methodName, error) {
this.debugContext?.logError(error);
}
};

@@ -623,14 +719,30 @@

// src/utils/method-spy.ts
function createMethodSpy(method, context, options = {}) {
({
...DEFAULT_FUNCTION_OPTIONS,
...options
function createMethodSpy(target, methodName, options = {}, originalMethod) {
const tracker = new MethodTracker(target, options);
const method = originalMethod || target[methodName];
const spy = createSpy();
if (!options.preservePrototype) {
return spy;
}
spy.mockImplementation(function(...args) {
tracker.trackMethodCall(methodName, args);
try {
const result = method.apply(this, args);
if (result instanceof Promise) {
return result.then((asyncResult) => {
tracker.trackMethodReturn(methodName, asyncResult);
return asyncResult;
}).catch((error) => {
tracker.trackError(methodName, error);
throw error;
});
}
tracker.trackMethodReturn(methodName, result);
return result;
} catch (error) {
tracker.trackError(methodName, error);
throw error;
}
});
const spy = new UniversalSpy();
const spyFn = spy.getSpy();
spyFn.mockImplementation(function(...args) {
const result = method.apply(context || this, args);
return result;
});
return spyFn;
return spy;
}

@@ -645,3 +757,3 @@

const method = currentProto[methodName].bind(mockClass);
mockClass[methodName] = createMethodSpy(method, mockClass, options);
mockClass[methodName] = createMethodSpy(mockClass, methodName, options, method);
}

@@ -652,2 +764,13 @@ });

}
function createPropertyDescriptor(target, name, descriptor, config) {
const getterSpy = descriptor.get ? createMethodSpy(target, name, config, descriptor.get) : void 0;
const setterSpy = descriptor.set ? createMethodSpy(target, name, config, descriptor.set) : void 0;
return {
...descriptor,
get: getterSpy,
set: setterSpy,
configurable: true,
enumerable: descriptor.enumerable
};
}
function cls(target, options = {}) {

@@ -676,3 +799,3 @@ const config = {

...descriptor,
value: createMethodSpy(override, context, config)
value: createMethodSpy(context, name, config, override)
};

@@ -682,3 +805,3 @@ }

...descriptor,
value: config.preservePrototype ? createMethodSpy(value, context, config) : createSpy()
value: config.preservePrototype ? createMethodSpy(context, name, config, value) : createSpy()
};

@@ -689,4 +812,4 @@ }

const setterOverride = typeof name === "string" && config.overrides?.[`set${name.charAt(0).toUpperCase()}${name.slice(1)}`];
const getterSpy = get && (getterOverride ? createMethodSpy(getterOverride, context, config) : config.preservePrototype ? createMethodSpy(get, context, config) : createSpy());
const setterSpy = set && (setterOverride ? createMethodSpy(setterOverride, context, config) : config.preservePrototype ? createMethodSpy(set, context, config) : createSpy());
const getterSpy = get && (getterOverride ? createMethodSpy(context, name, config, getterOverride) : config.preservePrototype ? createMethodSpy(context, name, config, get) : createSpy());
const setterSpy = set && (setterOverride ? createMethodSpy(context, name, config, setterOverride) : config.preservePrototype ? createMethodSpy(context, name, config, set) : createSpy());
return {

@@ -704,11 +827,7 @@ ...descriptor,

const descriptor = Object.getOwnPropertyDescriptor(source, name);
const newDescriptor = handleDescriptor(name, descriptor, target2);
if (!config.mockInPlace || !target2.hasOwnProperty(name)) {
Object.defineProperty(target2, name, newDescriptor);
} else {
if (typeof source[name] === "function") {
target2[name] = createMethodSpy(source[name], target2, config);
} else {
Object.defineProperty(target2, name, newDescriptor);
}
if (typeof source[name] === "function") {
const override = config.overrides?.[name];
target2[name] = createMethodSpy(target2, name, config, override || source[name]);
} else if (descriptor) {
Object.defineProperty(target2, name, createPropertyDescriptor(target2, name, descriptor, config));
}

@@ -723,5 +842,5 @@ });

if (typeof proto[name] === "function") {
proto[name] = createMethodSpy(proto[name], proto, config);
proto[name] = createMethodSpy(proto, name, config, proto[name]);
} else if (descriptor) {
Object.defineProperty(proto, name, handleDescriptor(name, descriptor, proto));
Object.defineProperty(proto, name, createPropertyDescriptor(proto, name, descriptor, config));
}

@@ -732,7 +851,7 @@ });

if (typeof target[name] === "function") {
target[name] = createMethodSpy(target[name], target, config);
target[name] = createMethodSpy(target, name, config, target[name]);
} else {
const descriptor = Object.getOwnPropertyDescriptor(target, name);
if (descriptor) {
Object.defineProperty(target, name, handleDescriptor(name, descriptor, target));
Object.defineProperty(target, name, createPropertyDescriptor(target, name, descriptor, config));
}

@@ -964,4 +1083,5 @@ }

const result = obj(target, config);
if (options.replace) {
Object.entries(options.replace).forEach(([key, impl]) => {
const objOptions = options;
if (objOptions.replace) {
Object.entries(objOptions.replace).forEach(([key, impl]) => {
if (impl && typeof impl === "function") {

@@ -1099,2 +1219,3 @@ replace(result, key, impl);

this.currentOptions = { ...DEFAULT_BASE_OPTIONS, ...options };
this.debugContext = createDebugContext(options);
}

@@ -1105,2 +1226,4 @@ /**

fn(_options) {
this.debugContext.logMockCreation("function");
this.debugContext.logMockConfig(_options);
const mockFn = fn();

@@ -1114,2 +1237,4 @@ this.registerMock(mockFn, "function");

obj(target, options = {}) {
this.debugContext.logMockCreation("object");
this.debugContext.logMockConfig(options);
const config = createObjectOptions({ ...this.currentOptions, ...options });

@@ -1124,2 +1249,4 @@ const mockObj = obj(target, config);

cls(target, options) {
this.debugContext.logMockCreation("class");
this.debugContext.logMockConfig(options);
const config = createClassOptions({ ...this.currentOptions, ...options });

@@ -1126,0 +1253,0 @@ const mockCls = cls(target, config);

2

package.json
{
"name": "@corez/mock",
"version": "0.4.0",
"version": "0.5.0",
"description": "A powerful and flexible TypeScript mocking library for testing",

@@ -5,0 +5,0 @@ "keywords": [

@@ -20,2 +20,3 @@ # @corez/mock

- [License](#license)
- [Debugging Features](#debugging-features)

@@ -669,1 +670,275 @@ ## Features

```
## Mock Composition
### Compose Function Options
The `compose` function provides a unified interface for creating mocks of different types (classes, objects, and
functions). It uses a sophisticated type system to provide appropriate options based on the target type.
#### Options Structure
```typescript
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;
};
});
```
#### Design Decisions
1. **Replace vs Override**
- `replace` is specifically designed for object mocking, allowing direct method replacement
- `override` is used in class mocking for overriding prototype methods
- While they may seem similar, they serve different purposes and contexts
2. **Type Safety**
- The options type automatically adapts based on the target type
- Class mocks receive `ClassMockOptions`
- Object mocks receive `ObjectMockOptions` with additional `replace` capability
- This ensures type safety while maintaining flexibility
3. **Usage Examples**
```typescript
// Class mocking
class Service {
getData(): string {
return 'data';
}
}
const mockService = compose(Service, {
override: {
getData: () => 'mocked',
},
});
// Object mocking with replace
const obj = {
method: () => 'original',
};
const mockObj = compose(obj, {
replace: {
method: () => 'replaced',
},
});
```
4. **Best Practices**
- Use `override` for class method mocking
- Use `replace` for direct object method replacement
- Consider the scope and context when choosing between them
- Avoid mixing `override` and `replace` in the same mock
## Debugging Features
### Debug Options
The library provides comprehensive debugging capabilities through the `debug` option and `name` property:
```typescript
interface DebugOptions {
debug?: boolean; // Enable/disable debugging
name?: string; // Name for the mock instance
level?: DebugLevel; // Debug level (ERROR, WARN, INFO, DEBUG, TRACE)
logPrefix?: string; // Custom prefix for log messages
logToConsole?: boolean; // Whether to log to console
customLogger?: (message: string, level: DebugLevel) => void; // Custom logging function
}
// Usage example
const userService = mock.compose<UserService>(
{},
{
debug: true,
name: 'UserService',
level: DebugLevel.DEBUG,
logPrefix: '[TEST]',
},
);
// Will output logs like:
// [TEST][UserService] Method getUser called with args: [1]
// [TEST][UserService] Method getUser returned: {"id":1,"name":"Test"}
```
### Debug Levels
The library supports different debug levels for fine-grained control:
- `ERROR`: Only critical errors
- `WARN`: Warnings and errors
- `INFO`: General information (default)
- `DEBUG`: Detailed debugging information
- `TRACE`: Most verbose level
### What Gets Logged
When debugging is enabled, the following information is logged:
1. **Method Calls**:
- Method name
- Arguments passed
- Return values
- Execution time (for async methods)
2. **Mock Creation**:
- Type of mock (function/object/class)
- Configuration options used
- Initial state
3. **Errors and Warnings**:
- Error messages
- Stack traces (in DEBUG level)
- Warning conditions
### Custom Logging
You can provide your own logging implementation:
```typescript
const customLogger = (message: string, level: DebugLevel) => {
// Send to your logging service
myLoggingService.log({
message,
level,
timestamp: new Date(),
});
};
const mock = mock.compose<UserService>(
{},
{
debug: true,
name: 'UserService',
customLogger,
},
);
```
### Best Practices for Debugging
1. **Use Meaningful Names**:
```typescript
// Good
const userServiceMock = mock.compose<UserService>({}, {name: 'UserService'});
// Not as helpful
const mock1 = mock.compose<UserService>({});
```
2. **Set Appropriate Debug Levels**:
```typescript
// Development/Testing
const devMock = mock.compose<UserService>(
{},
{
debug: true,
level: DebugLevel.DEBUG,
},
);
// Production Tests
const prodMock = mock.compose<UserService>(
{},
{
debug: true,
level: DebugLevel.ERROR,
},
);
```
3. **Use Custom Logging for CI/CD**:
```typescript
const ciLogger = (message: string, level: DebugLevel) => {
if (process.env.CI) {
// Format for CI system
process.stdout.write(`::${level}::${message}\n`);
}
};
```
## Logging and Debugging
### Logging Configuration
The library provides comprehensive logging capabilities to help with debugging and troubleshooting. You can configure
logging at different levels:
```typescript
import {mock, LogLevel} from '@corez/mock';
// Simple debug mode
const mockFn = mock.fn({
debug: true, // Enable detailed logging
});
// Advanced logging configuration
const userService = mock.obj<UserService>({
logging: {
level: LogLevel.DEBUG, // Set log level
prefix: '[UserService]', // Custom prefix for log messages
consoleOutput: true, // Enable console output
formatter: (msg, level, ctx) => {
return `[${new Date().toISOString()}] ${level}: ${msg}`;
},
logger: (msg, level) => {
// Custom logging implementation
customLogger.log(msg, level);
},
},
});
```
### Log Levels
The library supports the following log levels:
- `LogLevel.NONE` (0) - Disable all logging
- `LogLevel.ERROR` (1) - Only log errors
- `LogLevel.WARN` (2) - Log warnings and errors
- `LogLevel.INFO` (3) - Log general information (default)
- `LogLevel.DEBUG` (4) - Log detailed debug information
- `LogLevel.TRACE` (5) - Log everything including internal details
### Logging Options
| Option | Type | Default | Description |
| --------------- | -------- | ------------------ | ------------------------------------ |
| `debug` | boolean | false | Quick way to enable detailed logging |
| `level` | LogLevel | LogLevel.INFO | Controls log verbosity |
| `prefix` | string | mock name | Custom prefix for log messages |
| `consoleOutput` | boolean | true if debug=true | Whether to write logs to console |
| `formatter` | Function | undefined | Custom log message formatter |
| `logger` | Function | console.log | Custom logging implementation |
### Debug Information
When logging is enabled, the library will output information about:
- Mock creation and configuration
- Function calls and arguments
- Return values and errors
- Mock state changes
- Spy tracking information
- Method overrides and restorations
Example debug output:
```
[UserService] DEBUG: Creating mock object
[UserService] INFO: Method 'getUser' called with args: [1]
[UserService] DEBUG: Return value: { id: 1, name: 'Mock User' }
[UserService] WARN: Method 'updateUser' not implemented
```

@@ -24,2 +24,3 @@ import {

} from './types';
import {createDebugContext, DebugContext} from './utils/debug';

@@ -32,2 +33,3 @@ /**

private currentOptions: BaseMockOptions;
private debugContext: DebugContext;

@@ -37,2 +39,3 @@ constructor(options: Partial<BaseMockOptions> = {}) {

this.currentOptions = {...DEFAULT_BASE_OPTIONS, ...options};
this.debugContext = createDebugContext(options);
}

@@ -44,2 +47,4 @@

fn<T extends Fn>(_options?: Partial<FunctionMockOptions>): MockFunction<T> {
this.debugContext.logMockCreation('function');
this.debugContext.logMockConfig(_options);
const mockFn = fn<T>();

@@ -54,2 +59,4 @@ this.registerMock(mockFn, 'function');

obj<T extends object>(target: T | undefined, options: ObjectMockOptions<T> = {}): MockObject<T> {
this.debugContext.logMockCreation('object');
this.debugContext.logMockConfig(options);
const config = createObjectOptions({...this.currentOptions, ...options});

@@ -65,2 +72,4 @@ const mockObj = obj(target, config);

cls<T extends Constructor<any>>(target: T, options?: ClassMockOptions<InstanceType<T>>): ClsMock<T> {
this.debugContext.logMockCreation('class');
this.debugContext.logMockConfig(options);
const config = createClassOptions({...this.currentOptions, ...options});

@@ -67,0 +76,0 @@ const mockCls = cls(target, config);

import {DEFAULT_CLASS_OPTIONS} from '../defaults';
import {createSpy} from '../spy';
import {ClassMockOptions, ClsMock} from '../types';
import {ClassMockOptions, ClsMock, Constructor} from '../types';
import {createMethodSpy} from '../utils/method-spy';

@@ -19,3 +19,3 @@

const method = currentProto[methodName].bind(mockClass);
mockClass[methodName] = createMethodSpy(method, mockClass, options);
mockClass[methodName] = createMethodSpy(mockClass, methodName, options, method);
}

@@ -27,2 +27,20 @@ });

function createPropertyDescriptor<T>(
target: any,
name: string | symbol,
descriptor: PropertyDescriptor,
config: ClassMockOptions<T>,
): PropertyDescriptor {
const getterSpy = descriptor.get ? createMethodSpy(target, name as string, config, descriptor.get) : undefined;
const setterSpy = descriptor.set ? createMethodSpy(target, name as string, config, descriptor.set) : undefined;
return {
...descriptor,
get: getterSpy as (() => any) | undefined,
set: setterSpy as ((v: any) => void) | undefined,
configurable: true,
enumerable: descriptor.enumerable,
};
}
/**

@@ -49,3 +67,3 @@ * Creates a mock class with all methods and properties mocked while preserving the original class structure.

*/
export function cls<T extends new (...args: any[]) => any>(
export function cls<T extends Constructor<any>>(
target: T,

@@ -86,3 +104,3 @@ options: ClassMockOptions<InstanceType<T>> = {},

...descriptor,
value: createMethodSpy(override, context, config),
value: createMethodSpy(context, name as string, config, override),
};

@@ -92,3 +110,3 @@ }

...descriptor,
value: config.preservePrototype ? createMethodSpy(value, context, config) : createSpy(),
value: config.preservePrototype ? createMethodSpy(context, name as string, config, value) : createSpy(),
};

@@ -107,5 +125,5 @@ }

(getterOverride
? createMethodSpy(getterOverride, context, config)
? createMethodSpy(context, name as string, config, getterOverride)
: config.preservePrototype
? createMethodSpy(get, context, config)
? createMethodSpy(context, name as string, config, get)
: createSpy());

@@ -115,5 +133,5 @@ const setterSpy =

(setterOverride
? createMethodSpy(setterOverride, context, config)
? createMethodSpy(context, name as string, config, setterOverride)
: config.preservePrototype
? createMethodSpy(set, context, config)
? createMethodSpy(context, name as string, config, set)
: createSpy());

@@ -137,13 +155,7 @@

const descriptor = Object.getOwnPropertyDescriptor(source, name);
const newDescriptor = handleDescriptor(name, descriptor, target);
if (!config.mockInPlace || !target.hasOwnProperty(name)) {
Object.defineProperty(target, name, newDescriptor);
} else {
// When modifying original class, directly replace the method
if (typeof source[name] === 'function') {
target[name] = createMethodSpy(source[name], target, config);
} else {
Object.defineProperty(target, name, newDescriptor);
}
if (typeof source[name] === 'function') {
const override = config.overrides?.[name];
target[name] = createMethodSpy(target, name, config, override || source[name]);
} else if (descriptor) {
Object.defineProperty(target, name, createPropertyDescriptor(target, name, descriptor, config));
}

@@ -161,5 +173,5 @@ });

if (typeof proto[name] === 'function') {
(proto as any)[name] = createMethodSpy(proto[name], proto, config);
(proto as any)[name] = createMethodSpy(proto, name, config, proto[name]);
} else if (descriptor) {
Object.defineProperty(proto, name, handleDescriptor(name, descriptor, proto));
Object.defineProperty(proto, name, createPropertyDescriptor(proto, name, descriptor, config));
}

@@ -173,7 +185,7 @@ });

if (typeof (target as any)[name] === 'function') {
(target as any)[name] = createMethodSpy((target as any)[name], target, config);
(target as any)[name] = createMethodSpy(target, name, config, (target as any)[name]);
} else {
const descriptor = Object.getOwnPropertyDescriptor(target, name);
if (descriptor) {
Object.defineProperty(target, name, handleDescriptor(name, descriptor, target));
Object.defineProperty(target, name, createPropertyDescriptor(target, name, descriptor, config));
}

@@ -180,0 +192,0 @@ }

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

@@ -58,16 +58,6 @@ import * as fnModule from './function';

// Class mock overload
export function compose<T extends new (...args: any[]) => any>(
target: T,
options?: ClassMockOptions<InstanceType<T>>,
): ClsMock<T>;
export function compose<T extends new (...args: any[]) => any>(target: T, options?: ComposeOptions<T>): ClsMock<T>;
// Object mock overload
export function compose<T extends object>(
target: T,
options?: ObjectMockOptions<T> & {
replace?: {
[K in keyof T]?: T[K] extends Fn ? Fn : never;
};
},
): T;
export function compose<T extends object>(target: T, options?: ComposeOptions<T>): T;

@@ -77,7 +67,3 @@ // Implementation

target?: T | (new (...args: any[]) => any),
options: ObjectMockOptions<T> & {
replace?: {
[K in keyof T]?: T[K] extends Fn ? Fn : never;
};
} = {},
options: ComposeOptions<T> = {} as ComposeOptions<T>,
): T | ClsMock<any> | MockFunction<any> {

@@ -94,3 +80,3 @@ if (!target) {

...DEFAULT_CLASS_OPTIONS.overrides,
...options.overrides,
...(options as ClassMockOptions<any>).overrides,
},

@@ -108,4 +94,10 @@ };

if (options.replace) {
Object.entries(options.replace).forEach(([key, impl]) => {
const objOptions = options as ObjectMockOptions<T> & {
replace?: {
[K in keyof T]?: T[K] extends Fn ? Fn : never;
};
};
if (objOptions.replace) {
Object.entries(objOptions.replace).forEach(([key, impl]) => {
if (impl && typeof impl === 'function') {

@@ -112,0 +104,0 @@ replaceModule.replace(result, key as keyof T, impl as unknown as Fn);

@@ -14,2 +14,61 @@ import type {Constructor, DeepPartial, Fn, PropertyDescriptor, RecursivePartial} from './common';

/**
* Log level for controlling debug output verbosity
*/
export enum LogLevel {
NONE = 0,
ERROR = 1,
WARN = 2,
INFO = 3,
DEBUG = 4,
TRACE = 5,
}
/**
* Logging configuration options
*/
export interface LoggingOptions {
/**
* Whether to enable detailed logging
* @default false
*/
debug?: boolean;
/**
* Log level for controlling output verbosity
* If debug is true, defaults to LogLevel.DEBUG
* If debug is false, defaults to LogLevel.INFO
* @default LogLevel.INFO
*/
level?: LogLevel;
/**
* Custom prefix for log messages
* If not provided, uses the mock name if available
*/
prefix?: string;
/**
* Whether to output logs to console
* Controls if logs should be written to console or suppressed
* @default true if debug is true, false otherwise
*/
consoleOutput?: boolean;
/**
* Custom log formatter
* @param message The log message
* @param level Current log level
* @param context Additional context data
*/
formatter?: (message: string, level: LogLevel, context?: Record<string, any>) => string;
/**
* Custom logger implementation
* @param message Formatted log message
* @param level Current log level
*/
logger?: (message: string, level: LogLevel) => void;
}
/**
* Base configuration options for all mock types

@@ -21,3 +80,3 @@ * These options control general mock behavior

* Enables detailed logging of mock operations
* Useful for debugging and understanding mock behavior
* Shorthand for logging.debug = true
* @default false

@@ -28,2 +87,8 @@ */

/**
* Logging configuration
* If debug is true, this will be merged with {debug: true, level: LogLevel.DEBUG}
*/
logging?: LoggingOptions;
/**
* Maximum time to wait for async operations in milliseconds

@@ -34,2 +99,8 @@ * Used when mocking async methods and properties

asyncTimeout?: number;
/**
* Name for the mock instance
* Used in debug logs to identify the mock
*/
name?: string;
}

@@ -361,1 +432,13 @@

}
/**
* 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;
};
});

@@ -246,2 +246,63 @@ import {MethodTracker} from '../method-tracker';

});
describe('debug functionality', () => {
it('should initialize debug context when debug option is true', () => {
const trackerWithDebug = new MethodTracker(
{},
{
debug: true,
name: 'test-tracker',
},
);
const finish = trackerWithDebug.record('testMethod', [1, 2, 3]);
finish(undefined, 'result');
// Debug context is private, so we verify through behavior
const calls = trackerWithDebug.getCalls();
expect(calls[0]).toMatchObject({
method: 'testMethod',
args: [1, 2, 3],
returnValue: 'result',
});
});
it('should track method calls with debug context', () => {
const trackerWithDebug = new MethodTracker(
{},
{
debug: true,
name: 'test-tracker',
},
);
trackerWithDebug.trackMethodCall('testMethod', [1, 2, 3]);
trackerWithDebug.trackMethodReturn('testMethod', 'result');
// Since debug output goes to console, we don't test the actual output
// but verify the methods don't throw
expect(() => {
trackerWithDebug.trackMethodCall('anotherMethod', [4, 5, 6]);
trackerWithDebug.trackMethodReturn('anotherMethod', 'another result');
}).not.toThrow();
});
it('should track errors with debug context', () => {
const trackerWithDebug = new MethodTracker(
{},
{
debug: true,
name: 'test-tracker',
},
);
const error = new Error('test error');
trackerWithDebug.trackError('testMethod', error);
// Verify error tracking doesn't throw
expect(() => {
trackerWithDebug.trackError('anotherMethod', new Error('another error'));
}).not.toThrow();
});
});
});

@@ -1,59 +0,47 @@

import {DEFAULT_FUNCTION_OPTIONS} from '../defaults';
import {UniversalSpy} from '../spy';
import {Fn, FunctionMockOptions, MockFunction} from '../types';
import {createSpy} from '../spy';
import {Fn, MockFunction} from '../types';
import {MethodTracker} from './method-tracker';
/**
* Creates a spy for a method with proper context binding and tracking capabilities.
* This utility function is used internally by the mocking system to create method spies
* that maintain their execution context and track calls.
*
* @template T - The type of the method being spied on
* @param method - The original method to spy on
* @param context - The context (this) to bind the method to
* @param options - Optional configuration options
* @returns A spy function that wraps the original method
*/
export function createMethodSpy<T extends Fn>(
method: T,
context: any,
options: Partial<FunctionMockOptions> = {},
target: any,
methodName: string,
options: any = {},
originalMethod?: T,
): MockFunction<T> {
const _config = {
...DEFAULT_FUNCTION_OPTIONS,
...options,
};
const tracker = new MethodTracker(target, options);
const method = originalMethod || target[methodName];
const spy = createSpy<T>();
const spy = new UniversalSpy<T>();
const spyFn = spy.getSpy();
if (!options.preservePrototype) {
return spy;
}
spyFn.mockImplementation(function (this: any, ...args: Parameters<T>): ReturnType<T> {
const result = method.apply(context || this, args);
return result;
spy.mockImplementation(function (this: any, ...args: Parameters<T>): ReturnType<T> {
tracker.trackMethodCall(methodName, args);
try {
const result = method.apply(this, args);
// Handle async results
if (result instanceof Promise) {
return result
.then(asyncResult => {
tracker.trackMethodReturn(methodName, asyncResult);
return asyncResult;
})
.catch(error => {
tracker.trackError(methodName, error);
throw error;
}) as ReturnType<T>;
}
tracker.trackMethodReturn(methodName, result);
return result;
} catch (error) {
tracker.trackError(methodName, error as Error);
throw error;
}
} as T);
return spyFn;
return spy;
}
/**
* Creates a spy for a property accessor (getter/setter) with proper context binding.
* This utility function is used internally by the mocking system to create property spies
* that maintain their execution context and track access/modifications.
*
* @template T - The type of the property value
* @param getter - The original getter function
* @param setter - The original setter function
* @param context - The context (this) to bind the accessors to
* @param options - Optional configuration options
* @returns An object containing spy functions for get and set operations
*/
export function createPropertyMethodSpy<T>(
getter?: (() => T) | undefined,
setter?: ((value: T) => void) | undefined,
context?: any,
options: Partial<FunctionMockOptions> = {},
): {get?: MockFunction<() => T>; set?: MockFunction<(value: T) => void>} {
return {
get: getter ? createMethodSpy(getter, context, options) : undefined,
set: setter ? createMethodSpy(setter, context, options) : undefined,
};
}
import {MockError} from '../errors';
import {compareArrays} from './compare';
import {DebugContext} from './debug';
import {calculateStats, checkPerformanceConstraints, type PerformanceStats} from './performance';

@@ -57,3 +58,15 @@

private startTime?: number;
private debugContext?: DebugContext;
private target: any;
constructor(target?: any, options: any = {}) {
this.target = target || {};
if (options.debug) {
this.debugContext = new DebugContext({
name: options.name || (target && target.constructor ? target.constructor.name : 'anonymous'),
...options,
});
}
}
/**

@@ -221,2 +234,14 @@ * Starts tracking method calls

}
trackMethodCall(methodName: string, args: any[]): void {
this.debugContext?.logMethodCall(methodName, args);
}
trackMethodReturn(methodName: string, result: any): void {
this.debugContext?.logMethodReturn(methodName, result);
}
trackError(methodName: string, error: Error): void {
this.debugContext?.logError(error);
}
}

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