mm (mock mate)

repository·master·Indexed 41 days ago

https://github.com/node-modules/mm

A simple and flexible mocking and stubbing library for Node.js version 4.0.2. It allows developers to intercept module properties, simulate synchronous and asynchronous errors, and mock network requests (HTTP/HTTPS), fs access, and child_process.spawn with minimal boilerplate. Includes built-in spy capabilities to track call counts and arguments, as well as support for async disposable objects.

Tokens
3.5K
Snippets
16
Records
20
Agent score
50%

What's inside mm

  1. How spies work in mm

    master

    When a mocked property is a function, mm automatically attaches spy capabilities to it. Every time the function is called, mm updates the following properties on the function object:

    • .called: The number of times the function was called.
    • .calledArguments: An array of all arguments passed in every call.
    • .lastCalledArguments: The arguments from the most recent call.

    If you only need to track calls without changing the implementation, use mm.spy(target, 'propertyName').

    import { mm } from 'mm';
    
    const target = {
      async add(a, b) {
        return a + b;
      },
    };
    
    mm.data(target, 'add', 3);
    
    assert.equal(await target.add(1, 1), 3);
    assert.equal(target.add.called, 1);
    assert.deepEqual(target.add.calledArguments, [[ 1, 1 ]]);
    assert.deepEqual(target.add.lastCalledArguments, [ 1, 1 ]);
  2. Basic usage of mm for mocking

    master

    You can mock a module property by passing the module object, the property name, and a replacement function. This allows you to intercept calls and return custom values. Use mm.restore() to remove all mock effects and return the original implementation.

    import fs from 'node:fs';
    import { mm } from 'mm';
    
    mm(fs, 'readFileSync', function(filename) {
      return filename + ' content';
    });
    
    console.log(fs.readFileSync('《九评 Java》'));
    // => 《九评 Java》 content
    
    mm.restore();
    console.log(fs.readFileSync('《九评 Java》'));
    // => throw `Error: ENOENT, no such file or directory '《九评 Java》`
  3. Mocking synchronous data with .syncData() and .syncEmpty()

    master

    Use these to mock synchronous return values.

    • mm.syncData(module, propertyName, value): Forces the function to return value.
    • mm.syncEmpty(module, propertyName): Forces the function to return undefined (empty).

    Example:

    mm.syncData(fs, 'readFileSync', Buffer.from('some content'));
  4. Mocking class methods with .classMethod()

    master

    Use mm.classMethod(instance, method, mockMethod) to mock a method on a specific class instance. Note that this affects all instances of that class if the method is being intercepted at the prototype level, or specifically the provided instance depending on how mm is applied.

    class Foo {
      async fetch() {
        return 1;
      }
    }
    
    const foo = new Foo();
    mm.classMethod(foo, 'fetch', async () => {
      return 3;
    });
    
    assert(await foo.fetch() === 3);
  5. Mocking errors with .error() and .errorOnce()

    master

    Use these methods to simulate failures in asynchronous functions that follow the Node.js error-first callback pattern.

    • mm.error(module, propertyName, errerMessage, errorProperties): Always returns an error with the specified message.
    • mm.errorOnce(module, propertyName, errerMessage, errorProperties): Returns the error only on the first call, then restores original behavior.

    The error returned will have err.name === 'MockError'.

    import fs from 'node:fs';
    import { mm } from 'mm';
    
    mm.error(fs, 'readFile', 'mock fs.readFile return error');
    
    fs.readFile('/etc/hosts', 'utf8', function (err, content) {
      // err.name === 'MockError'
      // err.message === 'mock fs.readFile return error'
      console.log(err);
    
      mm.restore();
    });
  6. Mocking HTTP/HTTPS requests

    master

    mm provides specialized helpers for mocking the Node.js http and https modules.

    Mocking successful requests

    Use mm.http.request(mockUrl, mockResData, mockResHeaders) or mm.https.request(...) to simulate a successful response with specific data and headers.

    Mocking request errors

    Use mm.http.requestError(mockUrl, reqError, resError) or mm.https.requestError(...) to simulate errors.

    • reqError: Error emitted on the request object.
    • resError: Error that prevents the response from emitting an end event (e.g., a MockHttpResponseError).
    var { mm } = require('mm');
    var http = require('http');
    
    var mockURL = '/foo';
    var mockResData = 'mock data';
    var mockResHeaders = { server: 'mock server' };
    mm.http.request(mockURL, mockResData, mockResHeaders);
    
    http.get({ path: '/foo' }, function (res) {
      // res.headers will match mockResHeaders
    });
  7. Mocking callback data with .data() and .datas()

    master

    These methods are used to mock functions that use callbacks (e.g., Node.js core modules).

    • mm.data(module, propertyName, secondCallbackArg): Mocks the function to call the callback with null as the first argument and secondCallbackArg as the second argument.
    • mm.datas(module, propertyName, argsArray): Mocks the function to call the callback with null as the first argument, followed by the elements in argsArray.

    Example of .data():

    mm.data(fs, 'readFile', Buffer.from('some content'));
    // results in: callback(null, Buffer.from('some content'))
  8. Mocking synchronous errors with .syncError()

    master

    Use mm.syncError to mock functions that throw errors immediately (synchronous) rather than passing them to a callback.

    mm.syncError(module, propertyName, errerMessage, errorProperties)

    Example:

    mm.syncError(fs, 'readFileSync', 'mock fs.readFile return error', {code: 'ENOENT'});
    // results in: throw new Error('mock fs.readFile return error') with code 'ENOENT'
  9. Mocking async disposable objects with .dataWithAsyncDispose()

    master

    Supports the [Symbol.asyncDispose] pattern, allowing you to mock objects used with the await using syntax.

    mm.dataWithAsyncDispose(module, propertyName, promiseResolveArg)

    Example:

    mm.dataWithAsyncDispose(locker, 'tryLock', {
      locked: true,
    });
    
    await using lock = await locker.tryLock('foo-key');
    assert.equal(lock.locked, true);
  10. Mock child_process.spawn

    master

    The spawn method mocks child_process.spawn to return an EventEmitter that simulates process lifecycle events.

    • spawn(code, stdout, stderr, timeout): Returns an emitter that, after timeout milliseconds, emits stdout, stderr, close, and exit with the provided code.
    import { spawn } from 'mm';
    
    // Mock a process that exits with code 0 and prints 'hello'
    spawn(0, 'hello', '', 100);
  11. Mock synchronous function behavior with .syncData() and .syncError()

    master

    For non-async functions, use the sync prefixed methods to control immediate return values or exceptions.

    • .syncData(module, method, data): Forces the method to return data immediately.
    • .syncError(module, method, error, props): Forces the method to throw error immediately. error can be a string or an Error instance.
    • .syncEmpty(module, method): Shorthand for .syncData(module, method, undefined).
    import { syncData, syncError, syncEmpty } from 'mm';
    
    syncData(config, 'getTimeout', 5000);
    syncError(db, 'connect', 'Connection Failed');
    syncEmpty(logger, 'log');