MockTimers
History
Mocking timers is a technique commonly used in software testing to simulate and
control the behavior of timers, such as setInterval and setTimeout,
without actually waiting for the specified time intervals.
MockTimers is also able to mock the Date object.
The MockTracker provides a top-level timers export
which is a MockTimers instance.
timers.enable
History
timers.enable(enableOptions?): void
Enables timer mocking for the specified timers.
ObjectArray'setInterval', 'setTimeout', 'setImmediate',
and 'Date'. Default: ['setInterval', 'setTimeout', 'setImmediate', 'Date'].
If no array is provided, all time related APIs ('setInterval', 'clearInterval',
'setTimeout', 'clearTimeout', 'setImmediate', 'clearImmediate', and
'Date') will be mocked by default.Note: When you enable mocking for a specific timer, its associated clear function will also be implicitly mocked.
Note: Mocking Date will affect the behavior of the mocked timers
as they use the same internal clock.
Example usage without setting initial time:
import { mock } from 'node:test'; mock.timers.enable({ apis: ['setInterval'] });
const { mock } = require('node:test'); mock.timers.enable({ apis: ['setInterval'] });
The above example enables mocking for the setInterval timer and
implicitly mocks the clearInterval function. Only the setInterval
and clearInterval functions from node:timers,
node:timers/promises, and
globalThis will be mocked.
Example usage with initial time set
import { mock } from 'node:test'; mock.timers.enable({ apis: ['Date'], now: 1000 });
const { mock } = require('node:test'); mock.timers.enable({ apis: ['Date'], now: 1000 });
Example usage with initial Date object as time set
import { mock } from 'node:test'; mock.timers.enable({ apis: ['Date'], now: new Date() });
const { mock } = require('node:test'); mock.timers.enable({ apis: ['Date'], now: new Date() });
Alternatively, if you call mock.timers.enable() without any parameters:
All timers ('setInterval', 'clearInterval', 'setTimeout', 'clearTimeout',
'setImmediate', and 'clearImmediate') will be mocked. The setInterval,
clearInterval, setTimeout, clearTimeout, setImmediate, and
clearImmediate functions from node:timers, node:timers/promises, and
globalThis will be mocked. As well as the global Date object.
timers.reset(): void
This function restores the default behavior of all mocks that were previously
created by this MockTimers instance and disassociates the mocks
from the MockTracker instance.
Note: After each test completes, this function is called on
the test context's MockTracker.
import { mock } from 'node:test'; mock.timers.reset();
const { mock } = require('node:test'); mock.timers.reset();
timers[Symbol.dispose](): void
Calls timers.reset().
timers.tick(milliseconds?): void
Advances time for all mocked timers.
number1.Note: This diverges from how setTimeout in Node.js behaves and accepts
only positive numbers. In Node.js, setTimeout with negative numbers is
only supported for web compatibility reasons.
The following example mocks a setTimeout function and
by using .tick advances in
time triggering all pending timers.
import assert from 'node:assert'; import { test } from 'node:test'; test('mocks setTimeout to be executed synchronously without having to actually wait for it', (context) => { const fn = context.mock.fn(); context.mock.timers.enable({ apis: ['setTimeout'] }); setTimeout(fn, 9999); assert.strictEqual(fn.mock.callCount(), 0); // Advance in time context.mock.timers.tick(9999); assert.strictEqual(fn.mock.callCount(), 1); });
const assert = require('node:assert'); const { test } = require('node:test'); test('mocks setTimeout to be executed synchronously without having to actually wait for it', (context) => { const fn = context.mock.fn(); context.mock.timers.enable({ apis: ['setTimeout'] }); setTimeout(fn, 9999); assert.strictEqual(fn.mock.callCount(), 0); // Advance in time context.mock.timers.tick(9999); assert.strictEqual(fn.mock.callCount(), 1); });
Alternatively, the .tick function can be called many times
import assert from 'node:assert'; import { test } from 'node:test'; test('mocks setTimeout to be executed synchronously without having to actually wait for it', (context) => { const fn = context.mock.fn(); context.mock.timers.enable({ apis: ['setTimeout'] }); const nineSecs = 9000; setTimeout(fn, nineSecs); const threeSeconds = 3000; context.mock.timers.tick(threeSeconds); context.mock.timers.tick(threeSeconds); context.mock.timers.tick(threeSeconds); assert.strictEqual(fn.mock.callCount(), 1); });
const assert = require('node:assert'); const { test } = require('node:test'); test('mocks setTimeout to be executed synchronously without having to actually wait for it', (context) => { const fn = context.mock.fn(); context.mock.timers.enable({ apis: ['setTimeout'] }); const nineSecs = 9000; setTimeout(fn, nineSecs); const threeSeconds = 3000; context.mock.timers.tick(threeSeconds); context.mock.timers.tick(threeSeconds); context.mock.timers.tick(threeSeconds); assert.strictEqual(fn.mock.callCount(), 1); });
Advancing time using .tick will also advance the time for any Date object
created after the mock was enabled (if Date was also set to be mocked).
import assert from 'node:assert'; import { test } from 'node:test'; test('mocks setTimeout to be executed synchronously without having to actually wait for it', (context) => { const fn = context.mock.fn(); context.mock.timers.enable({ apis: ['setTimeout', 'Date'] }); setTimeout(fn, 9999); assert.strictEqual(fn.mock.callCount(), 0); assert.strictEqual(Date.now(), 0); // Advance in time context.mock.timers.tick(9999); assert.strictEqual(fn.mock.callCount(), 1); assert.strictEqual(Date.now(), 9999); });
const assert = require('node:assert'); const { test } = require('node:test'); test('mocks setTimeout to be executed synchronously without having to actually wait for it', (context) => { const fn = context.mock.fn(); context.mock.timers.enable({ apis: ['setTimeout', 'Date'] }); setTimeout(fn, 9999); assert.strictEqual(fn.mock.callCount(), 0); assert.strictEqual(Date.now(), 0); // Advance in time context.mock.timers.tick(9999); assert.strictEqual(fn.mock.callCount(), 1); assert.strictEqual(Date.now(), 9999); });
As mentioned, all clear functions from timers (clearTimeout, clearInterval,and
clearImmediate) are implicitly mocked. Take a look at this example using setTimeout:
import assert from 'node:assert'; import { test } from 'node:test'; test('mocks setTimeout to be executed synchronously without having to actually wait for it', (context) => { const fn = context.mock.fn(); // Optionally choose what to mock context.mock.timers.enable({ apis: ['setTimeout'] }); const id = setTimeout(fn, 9999); // Implicitly mocked as well clearTimeout(id); context.mock.timers.tick(9999); // As that setTimeout was cleared the mock function will never be called assert.strictEqual(fn.mock.callCount(), 0); });
const assert = require('node:assert'); const { test } = require('node:test'); test('mocks setTimeout to be executed synchronously without having to actually wait for it', (context) => { const fn = context.mock.fn(); // Optionally choose what to mock context.mock.timers.enable({ apis: ['setTimeout'] }); const id = setTimeout(fn, 9999); // Implicitly mocked as well clearTimeout(id); context.mock.timers.tick(9999); // As that setTimeout was cleared the mock function will never be called assert.strictEqual(fn.mock.callCount(), 0); });
Once you enable mocking timers, node:timers, node:timers/promises modules, and timers from the Node.js global context are enabled:
Note: Destructuring functions such as
import { setTimeout } from 'node:timers' is currently
not supported by this API.
import assert from 'node:assert'; import { test } from 'node:test'; import nodeTimers from 'node:timers'; import nodeTimersPromises from 'node:timers/promises'; test('mocks setTimeout to be executed synchronously without having to actually wait for it', async (context) => { const globalTimeoutObjectSpy = context.mock.fn(); const nodeTimerSpy = context.mock.fn(); const nodeTimerPromiseSpy = context.mock.fn(); // Optionally choose what to mock context.mock.timers.enable({ apis: ['setTimeout'] }); setTimeout(globalTimeoutObjectSpy, 9999); nodeTimers.setTimeout(nodeTimerSpy, 9999); const promise = nodeTimersPromises.setTimeout(9999).then(nodeTimerPromiseSpy); // Advance in time context.mock.timers.tick(9999); assert.strictEqual(globalTimeoutObjectSpy.mock.callCount(), 1); assert.strictEqual(nodeTimerSpy.mock.callCount(), 1); await promise; assert.strictEqual(nodeTimerPromiseSpy.mock.callCount(), 1); });
const assert = require('node:assert'); const { test } = require('node:test'); const nodeTimers = require('node:timers'); const nodeTimersPromises = require('node:timers/promises'); test('mocks setTimeout to be executed synchronously without having to actually wait for it', async (context) => { const globalTimeoutObjectSpy = context.mock.fn(); const nodeTimerSpy = context.mock.fn(); const nodeTimerPromiseSpy = context.mock.fn(); // Optionally choose what to mock context.mock.timers.enable({ apis: ['setTimeout'] }); setTimeout(globalTimeoutObjectSpy, 9999); nodeTimers.setTimeout(nodeTimerSpy, 9999); const promise = nodeTimersPromises.setTimeout(9999).then(nodeTimerPromiseSpy); // Advance in time context.mock.timers.tick(9999); assert.strictEqual(globalTimeoutObjectSpy.mock.callCount(), 1); assert.strictEqual(nodeTimerSpy.mock.callCount(), 1); await promise; assert.strictEqual(nodeTimerPromiseSpy.mock.callCount(), 1); });
In Node.js, setInterval from node:timers/promises
is an AsyncGenerator and is also supported by this API:
import assert from 'node:assert'; import { test } from 'node:test'; import nodeTimersPromises from 'node:timers/promises'; test('should tick five times testing a real use case', async (context) => { context.mock.timers.enable({ apis: ['setInterval'] }); const expectedIterations = 3; const interval = 1000; const startedAt = Date.now(); async function run() { const times = []; for await (const time of nodeTimersPromises.setInterval(interval, startedAt)) { times.push(time); if (times.length === expectedIterations) break; } return times; } const r = run(); context.mock.timers.tick(interval); context.mock.timers.tick(interval); context.mock.timers.tick(interval); const timeResults = await r; assert.strictEqual(timeResults.length, expectedIterations); for (let it = 1; it < expectedIterations; it++) { assert.strictEqual(timeResults[it - 1], startedAt + (interval * it)); } });
const assert = require('node:assert'); const { test } = require('node:test'); const nodeTimersPromises = require('node:timers/promises'); test('should tick five times testing a real use case', async (context) => { context.mock.timers.enable({ apis: ['setInterval'] }); const expectedIterations = 3; const interval = 1000; const startedAt = Date.now(); async function run() { const times = []; for await (const time of nodeTimersPromises.setInterval(interval, startedAt)) { times.push(time); if (times.length === expectedIterations) break; } return times; } const r = run(); context.mock.timers.tick(interval); context.mock.timers.tick(interval); context.mock.timers.tick(interval); const timeResults = await r; assert.strictEqual(timeResults.length, expectedIterations); for (let it = 1; it < expectedIterations; it++) { assert.strictEqual(timeResults[it - 1], startedAt + (interval * it)); } });
timers.runAll(): void
Triggers all pending mocked timers immediately. If the Date object is also
mocked, it will also advance the Date object to the furthest timer's time.
The example below triggers all pending timers immediately, causing them to execute without any delay.
import assert from 'node:assert'; import { test } from 'node:test'; test('runAll functions following the given order', (context) => { context.mock.timers.enable({ apis: ['setTimeout', 'Date'] }); const results = []; setTimeout(() => results.push(1), 9999); // Notice that if both timers have the same timeout, // the order of execution is guaranteed setTimeout(() => results.push(3), 8888); setTimeout(() => results.push(2), 8888); assert.deepStrictEqual(results, []); context.mock.timers.runAll(); assert.deepStrictEqual(results, [3, 2, 1]); // The Date object is also advanced to the furthest timer's time assert.strictEqual(Date.now(), 9999); });
const assert = require('node:assert'); const { test } = require('node:test'); test('runAll functions following the given order', (context) => { context.mock.timers.enable({ apis: ['setTimeout', 'Date'] }); const results = []; setTimeout(() => results.push(1), 9999); // Notice that if both timers have the same timeout, // the order of execution is guaranteed setTimeout(() => results.push(3), 8888); setTimeout(() => results.push(2), 8888); assert.deepStrictEqual(results, []); context.mock.timers.runAll(); assert.deepStrictEqual(results, [3, 2, 1]); // The Date object is also advanced to the furthest timer's time assert.strictEqual(Date.now(), 9999); });
Note: The runAll() function is specifically designed for
triggering timers in the context of timer mocking.
It does not have any effect on real-time system
clocks or actual timers outside of the mocking environment.
timers.setTime(milliseconds): void
Sets the current Unix timestamp that will be used as reference for any mocked
Date objects.
import assert from 'node:assert'; import { test } from 'node:test'; test('runAll functions following the given order', (context) => { const now = Date.now(); const setTime = 1000; // Date.now is not mocked assert.deepStrictEqual(Date.now(), now); context.mock.timers.enable({ apis: ['Date'] }); context.mock.timers.setTime(setTime); // Date.now is now 1000 assert.strictEqual(Date.now(), setTime); });
const assert = require('node:assert'); const { test } = require('node:test'); test('setTime replaces current time', (context) => { const now = Date.now(); const setTime = 1000; // Date.now is not mocked assert.deepStrictEqual(Date.now(), now); context.mock.timers.enable({ apis: ['Date'] }); context.mock.timers.setTime(setTime); // Date.now is now 1000 assert.strictEqual(Date.now(), setTime); });
Dates and timer objects are dependent on each other. If you use setTime() to
pass the current time to the mocked Date object, the set timers with
setTimeout and setInterval will not be affected.
However, the tick method will advance the mocked Date object.
import assert from 'node:assert'; import { test } from 'node:test'; test('runAll functions following the given order', (context) => { context.mock.timers.enable({ apis: ['setTimeout', 'Date'] }); const results = []; setTimeout(() => results.push(1), 9999); assert.deepStrictEqual(results, []); context.mock.timers.setTime(12000); assert.deepStrictEqual(results, []); // The date is advanced but the timers don't tick assert.strictEqual(Date.now(), 12000); });
const assert = require('node:assert'); const { test } = require('node:test'); test('runAll functions following the given order', (context) => { context.mock.timers.enable({ apis: ['setTimeout', 'Date'] }); const results = []; setTimeout(() => results.push(1), 9999); assert.deepStrictEqual(results, []); context.mock.timers.setTime(12000); assert.deepStrictEqual(results, []); // The date is advanced but the timers don't tick assert.strictEqual(Date.now(), 12000); });