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

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

The native timer functions (i.e., setTimeout(), setInterval(), clearTimeout(), clearInterval()) are less than ideal for a testing environment since they depend on real time to elapse. Jest вміє підміняти їх, щоб ви могли керувати часом. Great Scott!

info

Also see Fake Timers API documentation.

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

In the following example we enable fake timers by calling jest.useFakeTimers(). This is replacing the original implementation of setTimeout() and other timer functions. Timers can be restored to their normal behavior with 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 таймерів через припущення, що це нескінченний цикл!"

If that is your case, using jest.runOnlyPendingTimers() will solve the problem:

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

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

// Schedule the next game in 10 seconds
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);

// 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);

// 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});

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

Another possibility is use jest.advanceTimersByTime(msToRun). When this API is called, all timers are advanced by msToRun milliseconds. Всі макрозавдання заплановані через 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);
});

Зрештою, це може іноді бути корисним в деяких тестах, щоб мати можливість очистити всі активні таймери. For this, we have 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);
});