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

A powerful and flexible TypeScript mocking library for testing

  • 0.5.0
  • Source
  • npm
  • Socket score

Version published
Weekly downloads
12
increased by33.33%
Maintainers
0
Weekly downloads
 
Created
Source

@corez/mock

A powerful, flexible, and type-safe mocking library for TypeScript testing.

npm version License: Apache-2.0 Node Version pnpm

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

# Using npm
npm install @corez/mock --save-dev

# Using yarn
yarn add -D @corez/mock

# Using pnpm
pnpm add -D @corez/mock

Quick Start

import {mock} from '@corez/mock';

// Mock a function
const greet = mock.fn<(name: string) => string>();
greet.mockImplementation(name => `Hello, ${name}!`);

// Mock an object
interface User {
  id: number;
  name: string;
}

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 => {},
  },
  {
    overrides: {
      getUser: async id => ({id, name: 'Mock User'}),
    },
  },
);

// Mock a class
class Database {
  async connect() {
    /* ... */
  }
  async query(sql: string) {
    /* ... */
  }
}

// Create a new mock class
const MockDatabase = mock.cls(Database, {
  mockStatic: true,
  preserveConstructor: true,
});

const db = new MockDatabase();
db.query.mockResolvedValue({rows: []});

Core Concepts

Mock Functions

Create standalone mock functions with full tracking capabilities:

const mockFn = mock.fn<(x: number) => number>();

// Set implementation
mockFn.mockImplementation(x => x * 2);

// Set return value
mockFn.mockReturnValue(42);

// Handle async scenarios
mockFn.mockResolvedValue('result');
mockFn.mockRejectedValue(new Error('failed'));

// Verify calls
expect(mockFn.calls.count()).toBe(1);
expect(mockFn.calls.all()[0].args).toEqual([1]);

Mock Objects

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 => {},
});

// Track method calls
userService.getUser(1);
expect((userService.getUser as any).mock.calls.length).toBe(1);

// Override return values
(userService.getUser as any).mockResolvedValue({id: 1, name: 'Mock'});

// Access call information
const getUserMock = userService.getUser as MockFunction;
console.log(getUserMock.calls.count());
console.log(getUserMock.calls.all());

Mock Classes

Create mock classes with automatic method tracking:

class Database {
  async connect() {
    /* ... */
  }
  async query(sql: string) {
    /* ... */
  }
}

// Create a new mock class
const MockDatabase = mock.cls(Database, {
  overrides: {
    query: async () => [{id: 1}],
  },
});

const db = new MockDatabase();
// Access call information
const queryMock = db.query as MockFunction;
console.log(queryMock.calls.count());

// Or modify the original class
mock.cls(Database, {
  mockInPlace: true,
  overrides: {
    query: async () => [{id: 1}],
  },
});

// Store original methods for restoration
const originalQuery = Database.prototype.query;
const originalConnect = Database.prototype.connect;

// Later, restore original methods
Database.prototype.query = originalQuery;
Database.prototype.connect = originalConnect;

API Reference

Core APIs

mock.fn<T extends Fn = Fn>(): MockFunction<T>

Creates a mock function with tracking capabilities:

const mockFn = mock.fn<(x: number) => number>();

// Set implementation
mockFn.mockImplementation(x => x * 2);

// Set return value
mockFn.mockReturnValue(42);

// Handle async scenarios
mockFn.mockResolvedValue('result');
mockFn.mockRejectedValue(new Error('failed'));

// Verify calls
expect(mockFn.calls.count()).toBe(1);
expect(mockFn.calls.all()[0].args).toEqual([1]);
mock.obj<T extends object>(target: T | undefined, options?: ObjMockOptions<T>): MockObject<T>

Creates a mock object 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 => {},
  },
  {
    overrides: {
      getUser: async id => ({id, name: 'Mock User'}),
    },
  },
);

// Access call information
const getUserMock = userService.getUser as MockFunction;
console.log(getUserMock.calls.count());
console.log(getUserMock.calls.all());

// Verify specific calls
expect(getUserMock.calls.count()).toBe(1);
expect(getUserMock.calls.all()[0].args).toEqual([1]);
mock.cls<T extends Constructor<any>>(target: T, options?: ClsMockOptions<T>): ClsMock<T>

Creates a mock class with automatic method tracking:

class Database {
  async connect() {
    /* ... */
  }
  async query(sql: string) {
    /* ... */
  }
}

// Create a new mock class
const MockDatabase = mock.cls(Database, {
  overrides: {
    query: async () => [{id: 1}],
  },
});

const db = new MockDatabase();
// Access call information
const queryMock = db.query as MockFunction;
console.log(queryMock.calls.count());

// Or modify the original class
mock.cls(Database, {
  mockInPlace: true,
  overrides: {
    query: async () => [{id: 1}],
  },
});

// Store original methods for restoration
const originalQuery = Database.prototype.query;
const originalConnect = Database.prototype.connect;

// Later, restore original methods
Database.prototype.query = originalQuery;
Database.prototype.connect = originalConnect;
mock.compose<T extends Fn>(): MockFunction<T>;
mock.compose<T extends new (...args: any[]) => any>(target: T, options?: {overrides?: DeepPartial<InstanceType<T>>} & Partial<Config>): ClsMock<T>;
mock.compose<T extends object>(target: T, options?: {overrides?: DeepPartial<T>; replace?: {[K in keyof T]?: T[K] extends Fn ? Fn : never}} & Partial<Config>): T;
mock.compose<T extends object>(partialImpl: DeepPartial<T>, options?: Partial<Config>): T;

Creates a mock from a class constructor, object, or function:

// Mock a function
const mockFn = mock.compose<(x: number) => string>();
mockFn.mockImplementation(x => x.toString());

// Mock a class
class Database {
  async query(sql: string) {
    /* ... */
  }
}

const MockDatabase = mock.compose(Database, {
  overrides: {
    query: async () => [{id: 1}],
  },
});

// Mock an object
interface Api {
  fetch(url: string): Promise<any>;
}

const api = mock.compose<Api>(
  {
    fetch: async url => ({data: []}),
  },
  {
    overrides: {
      fetch: async url => ({data: [{id: 1}]}),
    },
  },
);

// Mock with partial implementation
interface ComplexApi {
  getUsers(): Promise<User[]>;
  getUser(id: number): Promise<User>;
  createUser(user: User): Promise<void>;
}

const partialApi = mock.compose<ComplexApi>({
  getUsers: async () => [{id: 1, name: 'John'}],
  getUser: async id => ({id, name: 'John'}),
});
mock.cast<T extends object>(partial: DeepPartial<T>, options?: Partial<Config>): T

Casts a partial implementation to a complete mock:

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>;
}

// Only implement the methods we need
const api = mock.cast<CompleteApi>({
  getUsers: async () => [{id: 1, name: 'John'}],
  getUser: async id => ({id, name: 'John'}),
});

// All other methods will be automatically mocked
await api.createUser({id: 1, name: 'Test'}); // Works, returns undefined
await api.updateUser({id: 1, name: 'Test'}); // Works, returns undefined
await api.deleteUser(1); // Works, returns undefined

// Access call information
const createUserMock = api.createUser as MockFunction;
expect(createUserMock.calls.count()).toBe(1);
mock.replace<T extends object, K extends keyof T>(obj: T, key: K, impl: Fn, options?: Partial<Config>): void

Replaces methods while preserving original implementation:

class Service {
  async getData() {
    /* ... */
  }

  async processData(data: any) {
    /* ... */
  }
}

const service = new Service();

// Replace single method
mock.replace(service, 'getData', async () => ['mocked']);

// Verify the method was replaced
expect(await service.getData()).toEqual(['mocked']);

// Original processData method remains unchanged
expect(service.processData).toBeDefined();

// Access call information
const getDataMock = service.getData as unknown as MockFunction;
expect(getDataMock.calls.count()).toBe(1);

// Restore original implementation
mock.restore(service);

Mock Control

All mocks provide these control methods:

// Clear call history
mock.mockClear(); // Clears calls but keeps implementation

// Reset completely
mock.mockReset(); // Clears everything

// Restore original
mock.mockRestore(); // Restores original implementation

// Access state
mock.calls.all(); // Call arguments with context
mock.calls.count(); // Number of calls

Advanced Usage

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();

// Using replace for method replacement
mock.replace(service, 'getUser', async id => ({
  id,
  name: 'Mock User',
}));

// Using overrides for partial implementation
const partialService = mock.obj<UserService>(
  {},
  {
    overrides: {
      getUser: async id => ({id, name: 'Mock User'}),
    },
  },
);

Best Practices

  1. Reset Between Tests

    beforeEach(() => {
      mockFn.mockReset();
      // or
      mockObj.mockReset();
    });
    
  2. Type Safety

    // Prefer interfaces for better type inference
    interface Service {
      method(): string;
    }
    const mock = mock.obj<Service>();
    
  3. Error Handling

    // Always test error cases
    api.fetch.mockRejectedValue(new Error('Network error'));
    await expect(api.fetch()).rejects.toThrow('Network error');
    
  4. Verification

    // Verify call count and arguments
    expect(mockFn.calls.count()).toBe(1);
    expect(mockFn.calls.all()[0].args).toEqual(['expected arg']);
    

Troubleshooting

Common Issues

  1. Mock Not Tracking Calls

    • For functions: Enable trackCalls in FunctionMockOptions
    • For objects and classes: Method calls are tracked automatically
    • Ensure you're accessing the mock properties correctly (e.g., mock.calls)
  2. Type Inference Issues

    • Use explicit type parameters with interfaces
    • Define complete interfaces for complex types
    • Use as MockFunction<T> for better type support
  3. Prototype Chain Issues

    • For classes: Use preservePrototype: true (default)
    • For objects: Use mockDeep: true to mock prototype methods
    • For type casting: Use keepPrototype: true (default)

Contributing

We welcome contributions! Please see our Contributing Guide for details.

License

Apache-2.0 - see LICENSE for details.

Mock Options

Base Options

All mock types support these basic options:

  • debug: Enables detailed logging of mock operations (default: false)
  • asyncTimeout: Maximum time to wait for async operations in ms (default: 5000)

Function Mock Options

Options for mocking functions:

  • trackCalls: Records arguments, return values, and call count (default: false)
  • autoSpy: Automatically creates spies for all function properties (default: false)

Object Mock Options

Options for mocking objects:

  • mockInPlace: Controls whether to modify the original object or create a new one (default: false)
  • mockDeep: Controls whether to perform deep cloning and mocking of nested objects and prototype chain methods (default: false)
  • overrides: Allows overriding specific properties or methods with custom implementations

Class Mock Options

Options for mocking classes:

  • mockInPlace: Controls whether to modify the original class or create a new one (default: false)
  • preservePrototype: Controls whether to preserve the prototype chain (default: true)
  • preserveConstructor: Controls whether to preserve the original constructor behavior (default: true)
  • mockStatic: Controls whether to mock static class members (default: false)
  • overrides: Allows overriding specific properties or methods with custom implementations

Cast Mock Options

Options for type casting and partial implementations:

  • keepPrototype: Maintains the prototype chain when casting objects (default: true)

Usage Examples

Basic Function Mocking

import {mock} from '@corez/mock';

// Create a mock function
const mockFn = mock.fn<() => string>();
mockFn.mockReturnValue('hello');

// Track calls
expect(mockFn()).toBe('hello');
expect(mockFn.mock.calls.length).toBe(1);

Object Mocking

import {mock} from '@corez/mock';

interface User {
  name: string;
  getId(): number;
}

// Create a mock object
const mockUser = mock.obj<User>({
  name: 'Test User',
  getId: () => 1,
});

// Override methods
mockUser.getId.mockReturnValue(2);

Class Mocking

import {mock} from '@corez/mock';

class Database {
  connect() {
    /* ... */
  }
  query(sql: string) {
    /* ... */
  }
}

// Create a mock class
const MockDatabase = mock.cls(Database, {
  mockStatic: true,
  preserveConstructor: true,
});

const db = new MockDatabase();
db.query.mockResolvedValue({rows: []});

Type Casting

import {mock} from '@corez/mock';

interface ComplexType {
  data: string;
  process(): Promise<void>;
}

// Cast partial implementation to full mock
const partial = {data: 'test'};
const mockObj = mock.cast<ComplexType>(partial);

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
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

// 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',
  },
});
  1. 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:

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:

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:
// Good
const userServiceMock = mock.compose<UserService>({}, {name: 'UserService'});

// Not as helpful
const mock1 = mock.compose<UserService>({});
  1. Set Appropriate Debug Levels:
// Development/Testing
const devMock = mock.compose<UserService>(
  {},
  {
    debug: true,
    level: DebugLevel.DEBUG,
  },
);

// Production Tests
const prodMock = mock.compose<UserService>(
  {},
  {
    debug: true,
    level: DebugLevel.ERROR,
  },
);
  1. Use Custom Logging for CI/CD:
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:

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

OptionTypeDefaultDescription
debugbooleanfalseQuick way to enable detailed logging
levelLogLevelLogLevel.INFOControls log verbosity
prefixstringmock nameCustom prefix for log messages
consoleOutputbooleantrue if debug=trueWhether to write logs to console
formatterFunctionundefinedCustom log message formatter
loggerFunctionconsole.logCustom 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

Keywords

FAQs

Package last updated on 11 Jan 2025

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts

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