0.23.1 โ€ข Published 10 months ago

@corez/mocker v0.23.1

Weekly downloads
-
License
Apache-2.0
Repository
github
Last release
10 months ago

@corez/mocker

A powerful TypeScript mocking library with flexible configuration and behavior control.

npm version License TypeScript

Features

  • ๐Ÿ”’ Fully type-safe API with TypeScript
  • ๐ŸŽญ Function, object and class mocking
  • ๐ŸŽช Flexible mock configuration
  • ๐ŸŽฒ Call tracking and verification
  • ๐Ÿ’พ Instance isolation and state management
  • ๐ŸŽฏ Getter/Setter mocking support
  • ๐Ÿ”„ Original implementation call-through
  • ๐Ÿ“ฆ Zero runtime dependencies
  • ๐Ÿ” Comprehensive error handling
  • ๐Ÿงช Async function support
  • ๐ŸŽจ Deep object comparison
  • ๐Ÿ”ง Chainable API

Table of Contents

Installation

# Using pnpm (recommended)
pnpm add -D @corez/mocker

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

# Using yarn
yarn add -D @corez/mocker

Requirements

  • Node.js 16.0.0 or later
  • TypeScript 5.0.0 or later

Quick Start

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

// Mock a simple function
const fn = mocker.fn();
fn.mockReturnValue(42);
console.log(fn()); // 42

// Mock an object with call tracking
const user = {getName: () => 'John'};
const mockedUser = mocker.object(user);
mockedUser.getName.mockReturnValue('Jane');
console.log(mockedUser.getName()); // Jane
console.log(mockedUser.getName.mock.calls.length); // 1

// Mock a class with instance tracking
class UserService {
  getUser(id: number) {
    return {id, name: 'John'};
  }
}
const MockedService = mocker.class(UserService);
const instance = new MockedService();
instance.getUser.mockReturnValue({id: 1, name: 'Jane'});
console.log(MockedService.mock.instances.length); // 1

Basic Usage

Function Mocking

// Basic mock function
const fn = mocker.fn();

// With implementation
const fn = mocker.fn((x: number) => x * 2);

// Return values
fn.mockReturnValue(42);
fn.mockReturnValueOnce(1);
fn.mockReturnValueOnce(2).mockReturnValue(3);

// Implementations
fn.mockImplementation(x => x * 2);
fn.mockImplementationOnce(x => x + 1);

// Async functions
const asyncFn = mocker.fn(async () => 'result');
asyncFn.mockResolvedValue('mocked');
asyncFn.mockRejectedValue(new Error('failed'));

// Reset and verification
fn.mockClear(); // Clear calls
fn.mockReset(); // Clear calls and implementation

Object Mocking

interface User {
  id: number;
  name: string;
  getFullName(): string;
}

const user: User = {
  id: 1,
  name: 'John',
  getFullName: () => 'John Doe',
};

// Mock with type safety
const mockedUser = mocker.object<User>(user);

// Mock methods
mockedUser.getFullName.mockReturnValue('Jane Doe');

// Call through to original implementation
const mockedWithCallThrough = mocker.object(user, {callThrough: true});
console.log(mockedWithCallThrough.getFullName()); // 'John Doe'

// Reset mocks
mockedUser.mockReset();

Class Mocking

class UserService {
  private api: ApiClient;

  constructor(api: ApiClient) {
    this.api = api;
  }

  async getUser(id: number): Promise<User> {
    return this.api.fetch(`/users/${id}`);
  }
}

// Mock entire class
const MockedService = mocker.class(UserService);
const instance = new MockedService();

// Mock specific method
instance.getUser.mockResolvedValue({id: 1, name: 'John'});

// Custom implementation
MockedService.mockImplementation(
  class extends UserService {
    async getUser(id: number) {
      return {id, name: 'Custom'};
    }
  },
);

// Reset class
MockedService.mockReset();

Instance Mocking

class User {
  constructor(public name: string) {}
  getName() {
    return this.name;
  }
}

// Create mocked instance with overrides
const mockedUser = mocker.instance(User, {
  name: 'Jane',
  getName: () => 'Jane Doe',
});

// Mock existing instance
const user = new User('John');
const mockedExisting = mocker.instance(user);

Advanced Usage

Call Through Behavior

class Calculator {
  add(a: number, b: number) {
    return a + b;
  }
}

// Mock with call through
const mockedCalc = mocker.object(new Calculator(), {
  callThrough: true,
});

// Calls original implementation
console.log(mockedCalc.add(2, 3)); // 5

// Still tracks calls
console.log(mockedCalc.add.mock.calls); // [[2, 3]]

Keeping Original Methods

const obj = {
  method1() {
    return 1;
  },
  method2() {
    return 2;
  },
};

// Keep specific methods original
const mocked = mocker.object(obj, {
  keepOriginal: ({property}) => property === 'method1',
});

// method1 remains original, method2 is mocked
console.log(mocked.method1()); // 1
console.log(mocked.method2()); // undefined

Assertions

@corez/mocker provides a powerful assertion API with various matching patterns and detailed error reporting.

Basic Assertions

// Call count assertions
assert.called(spy); // Called at least once
assert.notCalled(spy); // Never called
assert.calledOnce(spy); // Called exactly once
assert.calledTwice(spy); // Called exactly twice
assert.calledThrice(spy); // Called exactly three times
assert.callCount(spy, n); // Called n times

// Argument assertions
assert.calledWith(spy, ...args); // Called with matching arguments
assert.calledWithExactly(spy, ...args); // Called with exact arguments
assert.calledOnceWith(spy, ...args); // Called once with matching arguments

Matchers

@corez/mocker supports three types of matchers:

  1. Built-in Matchers
import {matchers} from '@corez/mocker';

// Type matching
assert.calledWith(
  spy,
  matchers.string(), // Match any string
  matchers.number(), // Match any number
  matchers.boolean(), // Match any boolean
  matchers.any(), // Match any value
);

// Array matching
assert.calledWith(
  spy,
  matchers.arrayContaining([1, 2]), // Array contains specified elements
);

// Object matching
assert.calledWith(
  spy,
  matchers.objectContaining({
    // Object contains specified properties
    id: matchers.number(),
    name: matchers.string(),
  }),
);
  1. Jest-style Matchers
assert.calledWith(spy,
  expect.any(Number),              // Type matching
  expect.stringMatching(/test/),   // RegExp matching
  expect.objectContaining({...}),  // Object containing
  expect.arrayContaining([...])    // Array containing
);
  1. Custom Matchers
assert.calledWith(spy, {
  asymmetricMatch: actual => actual.startsWith('test'),
});

Deep Matching

Support for nested objects and arrays deep matching:

assert.calledWith(spy, {
  user: {
    id: matchers.number(),
    profile: {
      name: matchers.string(),
      tags: matchers.arrayContaining(['admin']),
      meta: matchers.objectContaining({
        verified: matchers.boolean(),
      }),
    },
  },
});

Error Assertions

Multiple error matching patterns:

// String matching
assert.threw(spy, 'error message');

// RegExp matching
assert.threw(spy, /error/);

// Constructor matching
assert.threw(spy, TypeError);

// Property matching
assert.threw(spy, {
  message: 'error message',
  code: 'ERR_001',
});

// Custom matching
assert.threw(spy, {
  asymmetricMatch: error => error.code === 'ERR_001',
});

Context Assertions

// this context assertions
assert.calledOn(spy, thisValue);

// Constructor call assertions
assert.calledWithNew(spy);

Error Formatting

When assertions fail, detailed error reports are generated:

// Call: spy({name: 'test', age: 25})
assert.calledWith(spy, {
  name: 'other',
  age: 30,
});

// Error message:
// AssertionError: expected spy to have been called with specific arguments
// Call 1:
// Expected: [{"name":"other","age":30}]
// Received: [{"name":"test","age":25}]
//
// Difference at index 0:
//   Expected: {"name":"other","age":30}
//   Received: {"name":"test","age":25}

Best Practices

  1. Reset Mocks Between Tests
beforeEach(() => {
  mocker.reset(mockedFunction);
  mockedObject.mockReset();
  MockedClass.mockReset();
});
  1. Type Safety
// Use type parameters for better type inference
const fn = mocker.fn<(x: number) => string>();
const obj = mocker.object<UserService>(service);
  1. Avoid Over-mocking
// Bad: Mocking everything
const mockedService = mocker.object(service);

// Good: Mock only what you need
const mockedService = mocker.object(service, {
  keepOriginal: ['helper1', 'helper2'],
});
  1. Use Call Through When Needed
// Only mock what needs to be mocked
const mockedService = mocker.object(service, {
  callThrough: true,
  keepOriginal: ['util1', 'util2'],
});

Contributing

Please read CONTRIBUTING.md for details on our code of conduct, and the process for submitting pull requests to us.

License

This project is licensed under the Apache-2.0 License - see the LICENSE file for details.

0.23.1

10 months ago

0.23.0

10 months ago

0.22.0

10 months ago

0.21.0

10 months ago

0.20.0

10 months ago

0.19.0

11 months ago

0.18.1

11 months ago

0.18.0

11 months ago

0.17.1

11 months ago

0.17.0

11 months ago

0.16.0

11 months ago