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)ort.equal(spy.callCount, n). - Arguments →
t.deepEqual(spy.mock.calls[i].arguments, [...])ort.deepEqual(spy.getCall(i).args, [...]). - Return values →
t.equal(spy.mock.calls[i].result, ...)(node:test) ort.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
- Assertion libraries — chai, node:assert, expect.
- Property-based testing — fast-check.