New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
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 0.4.0 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

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc