Перейти до основного змісту
Версія: 26.x

Імітації таймерів

Глобальні функції setTimeout, setInterval, clearTimeout, clearInterval є проблемою для тестового середовища, оскільки вони витрачають реальний час. Jest вміє підміняти їх, щоб ви могли керувати часом. Great Scott!

timerGame.js
'use strict';

function timerGame(callback) {
console.log('Ready....go!');
setTimeout(() => {
console.log("Time's up -- stop!");
callback && callback();
}, 1000);
}

module.exports = timerGame;
__tests__/timerGame-test.js
'use strict';

jest.useFakeTimers(); // or you can set "timers": "fake" globally in configuration file

test('waits 1 second before ending the game', () => {
const timerGame = require('../timerGame');
timerGame();

expect(setTimeout).toHaveBeenCalledTimes(1);
expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), 1000);
});

Here we enable fake timers by calling jest.useFakeTimers();. This mocks out setTimeout and other timer functions with mock functions. If running multiple tests inside of one file or describe block, jest.useFakeTimers(); can be called before each test manually or with a setup function such as beforeEach. Not doing so will result in the internal usage counter not being reset.

All of the following functions need fake timers to be set, either by jest.useFakeTimers() or via "timers": "fake" in the config file.

Currently, two implementations of the fake timers are included - modern and legacy, where legacy is still the default one. See configuration for how to configure it.

Запуск усіх таймерів

Інший тест, який ми можемо написати для цього модуля, це той, що перевіряє, що callback викликається через 1 секунду. Щоб швидко промотати час прямо в середині тесту, ми будемо використовувати API Jest для контролю таймерів:

jest.useFakeTimers();
test('calls the callback after 1 second', () => {
const timerGame = require('../timerGame');
const callback = jest.fn();

timerGame(callback);

// At this point in time, the callback should not have been called yet
expect(callback).not.toBeCalled();

// Fast-forward until all timers have been executed
jest.runAllTimers();

// Now our callback should have been called!
expect(callback).toBeCalled();
expect(callback).toHaveBeenCalledTimes(1);
});

Запуск активних таймерів

Є також сценарії, коли ви можете мати рекурсивний таймер -- це таймер, який запускає новий таймер у власному зворотному виклику. For these, running all the timers would be an endless loop, throwing the following error:

Ran 100000 timers, and there are still more! Assuming we've hit an infinite recursion and bailing out...

So something like jest.runAllTimers() is not desirable. Для таких випадків ви можете використовувати jest.runOnlyPendingTimers():

infiniteTimerGame.js
'use strict';

function infiniteTimerGame(callback) {
console.log('Ready....go!');

setTimeout(() => {
console.log("Time's up! 10 seconds before the next game starts...");
callback && callback();

// Запланувати наступну гру через 10 секунд
setTimeout(() => {
infiniteTimerGame(callback);
}, 10000);
}, 1000);
}

module.exports = infiniteTimerGame;
__tests__/infiniteTimerGame-test.js
'use strict';

jest.useFakeTimers();
jest.spyOn(global, 'setTimeout');

describe('infiniteTimerGame', () => {
test('schedules a 10-second timer after 1 second', () => {
const infiniteTimerGame = require('../infiniteTimerGame');
const callback = jest.fn();

infiniteTimerGame(callback);

// At this point in time, there should have been a single call to
// setTimeout to schedule the end of the game in 1 second.
expect(setTimeout).toHaveBeenCalledTimes(1);
expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), 1000);

// Промотати і відпрацювати тільки активні таймери
// (але не будь-які нові таймери, які будуть створені протягом цієї операції)
jest.runOnlyPendingTimers();

// На цей момент, наш секундний таймер має викликати свій callback
expect(callback).toBeCalled();

// І створити новий таймер, щоб почати гру знову через 10 секунд
expect(setTimeout).toHaveBeenCalledTimes(2);
expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), 10000);
});
});

Наближення таймерів у часі

Інша можливість - використовувати jest.advanceTimersByTime(msToRun). Коли викликається цей метод, всі таймери наближаються на msToRun мілісекунд. Всі макрозавдання заплановані через setTimeout() або setInterval() до виконання протягом цього періоду часу, будуть виконані. Крім того якщо ці макрозавдання запланують нові, які мають виконатися в межах того ж часового проміжку, вони теж будуть виконані допоки в черзі не залишиться завдань, що мають бути виконані впродовж msToRun мілісекунд.

timerGame.js
'use strict';

function timerGame(callback) {
console.log('Ready....go!');
setTimeout(() => {
console.log("Time's up -- stop!");
callback && callback();
}, 1000);
}

module.exports = timerGame;
__tests__/timerGame-test.js
jest.useFakeTimers();
it('calls the callback after 1 second via advanceTimersByTime', () => {
const timerGame = require('../timerGame');
const callback = jest.fn();

timerGame(callback);

// At this point in time, the callback should not have been called yet
expect(callback).not.toBeCalled();

// Fast-forward until all timers have been executed
jest.advanceTimersByTime(1000);

// Now our callback should have been called!
expect(callback).toBeCalled();
expect(callback).toHaveBeenCalledTimes(1);
});

Зрештою, це може іноді бути корисним в деяких тестах, щоб мати можливість очистити всі активні таймери. Для цього у нас є jest.clearAllTimers().

The code for this example is available at examples/timer.