Перейти до основного змісту
Версія: 27.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();
jest.spyOn(global, 'setTimeout');

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

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

Таким чином, викликаючи jest.useFakeTimers(), ми вмикаємо фальшиві таймери. Це підміняє setTimeout та інші функції таймера на фейкові реалізації. Функції-таймери можна повернути до своєї оригінальної поведінки викликом jest.useRealTimers().

Оскільки ви можете викликати jest.useFakeTimers() або jest.useRealTimers() у будь-якому місці (верхній рівень, всередині блока it, і т.д.), це глобальна операція, яка зачепить інші тести в тому ж файлі. Додатково, вам потрібно робити виклик jest.useFakeTimers() перед кожним тестом, щоб скинути внутрішні лічильники. Якщо ви не плануєте використовувати фейкові таймери в кожному з ваших тестів, ви захочете робити очистку вручну, бо інакше фейкові таймери будуть просочуватися до інших тестів:

afterEach(() => {
jest.useRealTimers();
});

test('do something with fake timers', () => {
jest.useFakeTimers();
// ...
});

test('do something with real timers', () => {
// ...
});

Currently, two implementations of the fake timers are included - modern and legacy, where modern is 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.