@litert/utils-test
v2.2.0
Published
The utility functions/classes/constants about test (using node:test) for NodeJS.
Readme
LiteRT/Utils - Test
The utility functions/classes/constants about test (using node:test) for NodeJS.
[TOC]
Requirement
- TypeScript v5.0.0 (or newer)
- Node.js v22.0.0 (or newer)
Installation
npm i @litert/utils-test --saveGetting Started
Both autoTick and autoTickMs work with the Node.js built-in timer mocking API
(ctx.mock.timers). Before using either function inside a test, you must enable timer
mocking on the test context and ensure node:timers/promises is imported in the correct
form.
Step 1 — Enable timer mocking
At the beginning of your test, call:
ctx.mock.timers.enable({ apis: ['setTimeout', 'Date'] });Include every timer API your code-under-test actually uses in the apis array
(e.g., 'setInterval', 'setImmediate', 'Date').
Step 2 — Import node:timers/promises correctly
[!IMPORTANT] In some old Node.js versions, the namespace import form (
import * as) bypasses timer mocking. Always use the default import form to guarantee that the timers are properly intercepted:// Correct — timers will be mocked import NodeTimer from 'node:timers/promises'; // Incorrect — timers may not be mocked in older Node.js versions import * as NodeTimer from 'node:timers/promises';
APIs
Function autoTick
Advances all pending mocked timers at once on every event loop tick until the given promise settles.
Internally, autoTick calls ctx.mock.timers.runAll() in a loop — once per event loop tick.
Each call to runAll() fires all currently pending timers, advancing the mock clock to the
time of the last scheduled timer in one step.
Signature:
function autoTick<T>(
ctx: TestContext,
asyncTask: Promise<T> | (() => Promise<T>),
): Promise<T>Parameters:
| Parameter | Type | Description |
| --- | --- | --- |
| ctx | TestContext | The test context provided by node:test, used to drive the mocked timers. |
| asyncTask | Promise<T> \| (() => Promise<T>) | The async task to run, passed either as an already-created Promise or as a factory function that returns one. |
Returns: Resolves with the same value as asyncTask, or rejects with the same error.
[!WARNING]
autoTickalways fires every pending timer at its full scheduled duration viarunAll(). If a timer is cancelled early by anAbortControllersignal, the mock clock still counts the full timer duration — not the elapsed time at the point of cancellation. UseautoTickMsinstead if accurate abort-signal timing matters to your test.
Example — basic usage:
import * as NodeTest from 'node:test';
import NodeTimer from 'node:timers/promises';
import { autoTick } from '@litert/utils-test';
NodeTest.it('resolves correctly with mocked timers', async (ctx) => {
ctx.mock.timers.enable({ apis: ['setTimeout', 'Date'] });
const result = await autoTick(ctx, async () => {
await NodeTimer.setTimeout(1000);
await NodeTimer.setTimeout(2000);
return 'done';
});
console.log(result); // 'done'
// The test completes instantly — no real time elapsed
});Example — passing a Promise directly:
NodeTest.it('accepts a pre-created promise', async (ctx) => {
ctx.mock.timers.enable({ apis: ['setTimeout', 'Date'] });
const task = (async () => {
await NodeTimer.setTimeout(500);
return 42;
})();
const result = await autoTick(ctx, task);
console.log(result); // 42
});Function autoTickMs
Advances the mocked clock by a fixed number of milliseconds on every event loop tick until the given promise settles.
Internally, autoTickMs calls ctx.mock.timers.tick(tickMs) in a loop — once per event loop
tick. Because the clock advances in discrete steps, the mock time elapsed when the promise
settles reflects only the ticks that actually ran up to that point, allowing abort-signal
timing to be precise.
Signature:
function autoTickMs<T>(
ctx: TestContext,
asyncTask: Promise<T> | (() => Promise<T>),
options?: IAutoTickMsOptions,
): Promise<T>Parameters:
| Parameter | Type | Description |
| --- | --- | --- |
| ctx | TestContext | The test context provided by node:test, used to drive the mocked timers. |
| asyncTask | Promise<T> \| (() => Promise<T>) | The async task to run, passed either as an already-created Promise or as a factory function that returns one. |
| options | IAutoTickMsOptions | Optional settings that control the ticking behaviour. |
Returns: Resolves with the same value as asyncTask, or rejects with the same error.
Options — IAutoTickMsOptions:
| Property | Type | Default | Description |
| --- | --- | --- | --- |
| tickMs | number | 1 | Milliseconds to advance the mock clock per event loop tick. Increase this for tests that involve long total timer durations to reduce the number of iterations and keep the test fast. |
[!NOTE] Because
autoTickMschecks whether the promise has settled only after each tick, at most one extra tick may occur after the promise settles. The final mock clock value might therefore be up totickMsmilliseconds ahead of the exact settlement point.
Example — basic usage:
import * as NodeTest from 'node:test';
import NodeTimer from 'node:timers/promises';
import { autoTickMs } from '@litert/utils-test';
NodeTest.it('resolves correctly with mocked timers', async (ctx) => {
ctx.mock.timers.enable({ apis: ['setTimeout', 'Date'] });
const result = await autoTickMs(ctx, async () => {
await NodeTimer.setTimeout(1000);
await NodeTimer.setTimeout(2000);
return 'done';
});
console.log(result); // 'done'
});Example — precise abort-signal timing:
NodeTest.it('timer aborted at 50ms counts only ~50ms of mock time', async (ctx) => {
ctx.mock.timers.enable({ apis: ['setTimeout', 'Date'] });
const t0 = Date.now();
const ac = new AbortController();
try {
await autoTickMs(ctx, async () => {
setTimeout(() => ac.abort(new Error('abort')), 50);
await NodeTimer.setTimeout(1000, null, { signal: ac.signal });
});
}
catch { // AbortError expected
}
// The promise settled when the abort fired at 50ms,
// so the mock clock advanced only ~50ms — not the full 1000ms
console.log(Date.now() - t0); // ~50
});Example — large tickMs for long-duration timers:
NodeTest.it('handles a 24-hour timer without performance issues', async (ctx) => {
ctx.mock.timers.enable({ apis: ['setTimeout', 'Date'] });
// Without tickMs override: tick(1) would run 86,400,000 times
// With tickMs = 3_600_000: tick(3_600_000) runs only 24 times
await autoTickMs(
ctx,
NodeTimer.setTimeout(3_600_000 * 24),
{ tickMs: 3_600_000 },
);
});Function withEnv
Temporarily sets environment variables for the duration of a callback and restores them afterward.
Works with both synchronous and asynchronous callbacks. If the callback returns a Promise, the environment variables are restored once the promise settles (resolves or rejects). Any error thrown by the callback is re-thrown after the environment variables are restored.
Signature:
function withEnv<T extends () => any>(env: IDict, cb: T): ReturnType<T>Parameters:
| Parameter | Type | Description |
| --- | --- | --- |
| env | IDict | An object of environment variable key-value pairs to set for the duration of the callback. |
| cb | T | The callback function to execute with the temporary environment variables. |
Returns: The return value of the callback (including Promise if the callback is async).
Example — synchronous callback:
import { withEnv } from '@litert/utils-test';
withEnv({ MY_VAR: 'hello' }, () => {
console.log(process.env.MY_VAR); // 'hello'
});
console.log(process.env.MY_VAR); // undefined (restored)Example — asynchronous callback:
import { withEnv } from '@litert/utils-test';
await withEnv({ NODE_ENV: 'test' }, async () => {
await someAsyncOperation();
console.log(process.env.NODE_ENV); // 'test'
});
console.log(process.env.NODE_ENV); // restored to originalChoosing Between autoTick and autoTickMs
| | autoTick | autoTickMs |
| --- | --- | --- |
| Timer advancement | Fires all pending timers at once (runAll()) | Advances by fixed tickMs increments (tick(ms)) |
| Speed | Fastest — constant iterations regardless of total duration | Proportional to total duration ÷ tickMs |
| Abort-signal precision | Full timer duration always counted | Counts only the ticks up to the abort point |
| Timer ordering control | Not possible | Possible with small tickMs |
| Long-duration timers | No special concern | Use a larger tickMs for efficiency |
Rule of thumb:
- Use
autoTickwhen you want the simplest possible way to advance all timers and don't care about abort-signal timing. - Use
autoTickMswhen you need to test abort/cancellation scenarios, timer ordering, or want the mock time to reflect reality as closely as possible.
Documentation
License
This library is published under Apache-2.0 license.
AI Disclaimer
This project may use AI tools to assist in documentation writing and inspiration for unit test cases, but all code is written and reviewed by human developers.
