The runner exposes a mock factory as mock on the module and as t.mock on the test context. Prefer the context version: its mocks are restored when the test ends, so a later test calling mailer.send hits the real method again. t.mock.method(object, 'name', replacement) swaps a method and records every call, giving it a .mock property with callCount(), calls and restore(); t.mock.fn() makes a standalone spy.
test('checkout emails the buyer', (t) => {
t.mock.method(mailer, 'send', () => 'queued');
assert.equal(checkout([{ price: 6, qty: 2 }], mailer), 12);
assert.equal(mailer.send.mock.callCount(), 1);
assert.deepEqual(mailer.send.mock.calls[0].arguments, [
'buyer@example.com', 'You owe 12',
]);
});Fake timers turn a test that would sleep for thirty seconds into one that finishes in a millisecond. Enable only the APIs you need — setTimeout, setInterval, setImmediate, Date — then advance the clock with tick(ms); global and node:timers/promises versions are both faked.
test('retry backs off without waiting', (t) => {
t.mock.timers.enable({ apis: ['setTimeout'] });
const wait = (ms) => new Promise((r) => setTimeout(r, ms));
let done = false;
wait(30_000).then(() => { done = true; });
t.mock.timers.tick(29_999);
assert.equal(done, false);
t.mock.timers.tick(1);
return Promise.resolve().then(() => assert.equal(done, true));
});Note the trailing Promise.resolve().then(...): tick fires the timer synchronously, but the .then still has to run as a microtask — the usual reason a fake-timer test fails on its last assertion. runAll() drains every pending timer at once. Module mocking replaces a whole specifier for everything imported after the call, which is how you cut a test off from the network:
test('the checkout page uses the mocked rate', async (t) => {
t.mock.module('../src/rates.js', {
namedExports: { usdToEur: async (amount) => amount * 0.9 },
});
const { usdToEur } = await import('../src/rates.js');
assert.equal(await usdToEur(100), 90);
});That run passes, but it opens with ExperimentalWarning: Module mocking is an experimental feature and might change at any time and needs --experimental-test-module-mocks on Node 25 and 26 — without the flag t.mock.module is not a function at all. Because it affects only imports made after it, the module under test must be pulled in with a dynamic import(). A design that takes its collaborators as arguments needs neither the API nor the flag.