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

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

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

info

Також див. документацію API імітацій таймерів.

Ввімкнення фіктивних таймерів

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

timerGame.js
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();
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);
});

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

Інший тест, який ми можемо написати для цього модуля, це той, що перевіряє, що 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.toHaveBeenCalled();

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

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

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

Є також сценарії, коли ви можете мати рекурсивний таймер – це таймер, який запускає новий таймер у власному зворотному виклику. Через це, запуск всіх таймерів був би нескінченним циклом, що кидав наступну помилку: "Аборт після виконання 100 000 таймерів через припущення, що це нескінченний цикл!"

Якщо це ваш випадок, використання jest.runOnlyPendingTimers() розв'яже проблему:

infiniteTimerGame.js
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
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);

// На цей момент, мав статись один виклик
// setTimeout, аби запланувати кінець гри через 1 секунду.
expect(setTimeout).toHaveBeenCalledTimes(1);
expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), 1000);

// Fast forward and exhaust only currently pending timers
// (but not any new timers that get created during that process)
jest.runOnlyPendingTimers();

// At this point, our 1-second timer should have fired its callback
expect(callback).toHaveBeenCalled();

// And it should have created a new timer to start the game over in
// 10 seconds
expect(setTimeout).toHaveBeenCalledTimes(2);
expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), 10000);
});
});
note

Для відлагодження чи з будь-якої іншої причини, ви можете змінити ліміт таймерів, що будуть виконані перед викидом помилки:

jest.useFakeTimers({timerLimit: 100});

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

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

timerGame.js
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.toHaveBeenCalled();

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

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

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

Selective Faking

Sometimes your code may require to avoid overwriting the original implementation of one or another API. If that is the case, you can use doNotFake option. For example, here is how you could provide a custom mock function for performance.mark() in jsdom environment:

/**
* @jest-environment jsdom
*/

const mockPerformanceMark = jest.fn();
window.performance.mark = mockPerformanceMark;

test('allows mocking `performance.mark()`', () => {
jest.useFakeTimers({doNotFake: ['performance']});

expect(window.performance.mark).toBe(mockPerformanceMark);
});