0.23.1 โ€ข Published 9 months ago

@corez/mocker v0.23.1

Weekly downloads
-
License
Apache-2.0
Repository
github
Last release
9 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

9 months ago

0.23.0

9 months ago

0.22.0

9 months ago

0.21.0

9 months ago

0.20.0

9 months ago

0.19.0

9 months ago

0.18.1

9 months ago

0.18.0

9 months ago

0.17.1

9 months ago

0.17.0

9 months ago

0.16.0

9 months ago