UNPKG

jest-time-helpers

Version:

Helpers you can use in tests that relate to the passage of time (i.e. code that involves setTimeout, setInterval, new Date(), Date.now(), etc)

176 lines 6.87 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.WEEK = exports.DAY = exports.HOUR = exports.MINUTE = exports.SECOND = exports.setupFakeTimers = exports.sleepUntil = exports.sleep = void 0; const assert = require("assert"); const util = require("util"); // Grab the setTimeout from global before jest overwrites it with useFakeTimers const setTimeoutBypassingFakes = global.setTimeout; /** * Wait a number of milliseconds of _real time_ (not mocked time); useful for * allowing the runloop or external systems to advance. * * @param ts - how long to sleep for. */ const sleep = (ms, unref = false) => { let timeout; const promise = new Promise((resolve) => { timeout = setTimeoutBypassingFakes(resolve, ms); }); if (unref) { timeout.unref(); } return Object.assign(promise, { timeout }); }; exports.sleep = sleep; /** * Polls until the condition passes. * * Polls the `condition` callback every `pollInterval` ms of real time. If/when * it returns a truthy value, resolves successfully. If `maxDuration` is * exceeded before `condition()` returns true, rejects with an error. * * @param condition - a callback function that should return true when no more sleep is required * @param maxDuration - an optional maximum duration to sleep * @param pollInterval - an optional period of how long we should wait between checks; lower values increase load on the server but may make tests pass faster, larger values are more efficient but increase test latency. */ async function sleepUntil(condition, maxDuration = 2000, pollInterval = 2) { assert.ok(pollInterval >= 1, "pollInterval must be >= 1 millisecond"); if (condition()) { // Already fine, no need to sleep return; } // Wait for condition to pass const start = Date.now(); while (Date.now() - start < maxDuration) { await exports.sleep(pollInterval); if (condition()) { // Success return; } } // maxDuration exceeded throw new Error(`Slept for ${Date.now() - start}ms but condition never passed`); } exports.sleepUntil = sleepUntil; /** * This is for letting the Node.js event loop advance, e.g. when `setTimeout` * has `await` in the chain. * * @internal */ async function aFewRunLoops(count = 5) { for (let i = 0; i < count; i++) { await exports.sleep(0); } } /** * Sets up fake timers and Date implementation within your Jest test file. You * should call this at the top of the file (**not** within a test or an * after/before); tests that need fake timers should go into their own test * file. * * Returns an object containing a `setTime` function which you can use to set * the current (fake) date within your test to a particular JavaScript * timestamp (number of milliseconds since 1970-01-01T00:00:00Z). * * Enables the `jest.useFakeTimers()` integration, and overwrites `global.Date` * with a custom function that automatically applies your test file's given * offset. */ function setupFakeTimers() { jest.useFakeTimers(); const OriginalDate = global.Date; /** The offset, in milliseconds, to apply to results from `Date.now()` */ let offset = 0; function fakeNow() { return OriginalDate.now() + offset; } /** * A copy of `Date`, but overrides `new Date()` and `Date.now()` to return a * date/timestamp factoring in `setTime()` calls. */ const FakeDate = function (...args) { if (args.length === 0) { // `new Date()` becomes `new Date(fakeNow())` return new OriginalDate(fakeNow()); } else if (args.length === 1) { // As before return new OriginalDate(args[0]); } else { // As before return new OriginalDate(args[0], args[1], args[2], args[3], args[4], args[5], args[6]); } }; // Copy static methods of Date, overriding Date.now() FakeDate.now = () => fakeNow(); FakeDate.parse = Date.parse; FakeDate.UTC = Date.UTC; /** * Sets the `offset` such that a call to `Date.now()` (or `new Date()`) would * return this timestamp if called immediately (but time continues to * progress as expected after this). Also advances the timers by the * difference from the previous `offset`, if positive. Setting time backwards * is allowed (like setting back the system clock on a computer), but will * not advance (or undo the advancement of) any timers. * * Since advancing the time a few hours might not run all the intermediary * code quite right, we actually step it up by a configurable increment * (defaults to one minute) at a time. Setting time backwards (no matter how * far back) is done all at once. * * @param timestamp - the target timestamp * @param increment - the maximum we should advance time by at once in order to step towards `timestamp` */ async function setTime(timestamp, increment = exports.MINUTE) { assert.strictEqual(typeof timestamp, "number", `Expected \`setTime\` to be passed a number of milliseconds, instead received '${util.inspect(timestamp)}'`); const finalOffset = timestamp - OriginalDate.now(); const advancement = finalOffset - offset; if (advancement < 0) { offset = finalOffset; } else { let previousOffset = offset; while (previousOffset + increment < finalOffset) { offset = previousOffset + increment; previousOffset = offset; jest.advanceTimersByTime(increment); await aFewRunLoops(); } if (previousOffset < finalOffset) { offset = finalOffset; jest.advanceTimersByTime(finalOffset - previousOffset); await aFewRunLoops(); } } } beforeEach(() => { offset = 0; global.Date = FakeDate; }); afterEach(() => { global.Date = OriginalDate; }); /** * Returns the real-world current timestamp (unaffected by fake timers). */ function realNow() { return OriginalDate.now(); } // In future we may add other methods such as `setTimeWithoutAdvancingTimers` // to emulate the system clock changing without real time elapsing. return { setTime, realNow }; } exports.setupFakeTimers = setupFakeTimers; /** One second in milliseconds */ exports.SECOND = 1000; /** One minute in milliseconds */ exports.MINUTE = 60 * exports.SECOND; /** One hour in milliseconds */ exports.HOUR = 60 * exports.MINUTE; /** One day in milliseconds */ exports.DAY = 24 * exports.HOUR; /** One week in milliseconds */ exports.WEEK = 7 * exports.DAY; //# sourceMappingURL=index.js.map