3rd‐party mock libraries - uhop/tape-six GitHub Wiki

3rd-party mock libraries

tape-six doesn't ship a mocking library. The pattern is bring-your-own: install the lib you want, use it to construct spies/stubs/fakes, and use tape-six's t.* assertions to inspect what the mock recorded.

This works because mocks don't usually throw — they record state. Your test asserts on that state.

node:test mock (recommended)

Built into Node 20.4+, Bun 1.0+, and Deno 2.0+. Zero install, exposes mock.fn, mock.method, mock.timers, and mock.module.

The mock namespace is independent of node:test's test runner — you can pull just the mock helper into a tape-six test:

import {mock} from 'node:test';
import test from 'tape-six';

test('mock.fn — spy records calls', t => {
  const spy = mock.fn();
  spy(1, 2);
  spy('x');
  t.equal(spy.mock.calls.length, 2);
  t.deepEqual(spy.mock.calls[0].arguments, [1, 2]);
  t.deepEqual(spy.mock.calls[1].arguments, ['x']);
});

test('mock.fn — stub return values', t => {
  const greet = mock.fn(() => 'default');
  t.equal(greet(), 'default');
  greet.mock.mockImplementationOnce(() => 'override-once');
  t.equal(greet(), 'override-once');
  t.equal(greet(), 'default');
});

test('mock.method — replace a method on an object', t => {
  const obj = {add: (a, b) => a + b};
  const spy = mock.method(obj, 'add', () => 99);
  t.equal(obj.add(2, 3), 99);
  t.equal(spy.mock.calls.length, 1);
  spy.mock.restore();
  t.equal(obj.add(2, 3), 5);
});

test('mock.timers — control setTimeout', t => {
  mock.timers.enable({apis: ['setTimeout']});
  let fired = false;
  setTimeout(() => {
    fired = true;
  }, 1000);
  t.notOk(fired, 'not fired before tick');
  mock.timers.tick(1000);
  t.ok(fired, 'fired after tick');
  mock.timers.reset();
});

API reference: Node mock docs.

Browser caveat: node:test is not available in browsers. If you need spies in browser tests, use sinon (below) with an importmap entry.

Cross-runtime caveat: as of 2026-05, mock is fully implemented in Node, partially in Deno (no mock.timers), and not implemented in Bun. Pin the test file to the node (or node + deno) section in tape6.importmap if you target multiple runtimes — see Set up tests § Per-runtime sections. Tape-six itself uses this pattern for tests/node/test-mock.js.

sinon

sinon works the same way: build a spy/stub/fake, run code, assert with t.* on spy.callCount, spy.firstCall.args, etc.

import sinon from 'sinon';
import test from 'tape-six';

test('sinon spy', t => {
  const spy = sinon.spy();
  spy(1, 2);
  t.equal(spy.callCount, 1);
  t.deepEqual(spy.firstCall.args, [1, 2]);
});

For browser tests, add to tape6.importmap:

{
  "tape6": {
    "importmap": {
      "imports": {
        "sinon": "/node_modules/sinon/pkg/sinon-esm.js"
      }
    }
  }
}

The exact path depends on which sinon build you've installed; check node_modules/sinon/package.json#exports.

Notes on assertions

Both libraries record state on the spy/stub object — they don't throw on unexpected calls. Use t.* to assert:

  • Call counts → t.equal(spy.mock.calls.length, n) or t.equal(spy.callCount, n).
  • Arguments → t.deepEqual(spy.mock.calls[i].arguments, [...]) or t.deepEqual(spy.getCall(i).args, [...]).
  • Return values → t.equal(spy.mock.calls[i].result, ...) (node:test) or t.equal(spy.returnValues[i], ...) (sinon).

Anything that surfaces an error path (a stub that throws, a spy that wraps a real method that throws) goes through tape-six's normal exception handling — see 3rd-party assertion libraries.

See also