Simulações de Temporizador
As funções nativas do temporizador (por exemplo, setTimeout()
, setInterval()
, clearTimeout()
, clearInterval()
) não são ideais para um ambiente de teste, pois dependem do tempo real para decorrer. Jest pode trocar temporizadores por funções que permitem controlar a passagem do tempo. Great Scott!
Veja também a documentação da API Fake Timers.
Habilitar Fake Timers
No exemplo a seguir, habilitamos os fake timers chamando jest.useFakeTimers()
. Isso substituirá a implementação original de setTimeout()
e outras funções de timer. Timers can be restored to their normal behavior with jest.useRealTimers()
.
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);
});
Executar todos os temporizadores
Another test we might want to write for this module is one that asserts that the callback is called after 1 second. To do this, we're going to use Jest's timer control APIs to fast-forward time right in the middle of the test:
jest.useFakeTimers();
test('calls the callback after 1 second', () => {
const timerGame = require('../timerGame');
const callback = jest.fn();
timerGame(callback);
// // Neste momento, o retorno de chamada ainda não deveria ter sido chamado
expect(callback).not.toHaveBeenCalled();
// Fast-forward until all timers have been executed
jest.runAllTimers();
// Agora nosso retorno de chamada deveria ter sido chamado!
expect(callback).toHaveBeenCalled();
expect(callback).toHaveBeenCalledTimes(1);
});
Executar Temporizadores Pendentes
Também há cenários em que você pode ter um timer recursivo – ou seja, um timer que define um novo timer em seu próprio retorno de chamada. Para estes, executar todos os temporizadores seria um loop infinito, gerando o seguinte erro: "Abortando após executar 100.000 temporizadores, assumindo um loop infinito!"
Se for esse o seu caso, usar jest.runOnlyPendingTimers()
resolverá o problema:
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;
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);
// Neste ponto, deveria ter havido uma única chamada para
// setTimeout para agendar o fim do jogo em 1 segundo.
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);
});
});
For debugging or any other reason you can change the limit of timers that will be run before throwing an error:
jest.useFakeTimers({timerLimit: 100});
Avance Temporizadores por Tempo
Outra possibilidade é usar o jest.advanceTimersByTime(msToRun)
. When this API is called, all timers are advanced by msToRun
milliseconds. When this API is called, all timers are advanced by msToRun
milliseconds. All pending "macro-tasks" that have been queued via setTimeout() or setInterval(), and would be executed during this time frame, will be executed.
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);
});
Lastly, it may occasionally be useful in some tests to be able to clear all of the pending timers. For this, we have jest.clearAllTimers()
.
Fake seletivo
Algumas vezes, seu código pode precisar evitar a sobrescrita da implementação de outra API. Se esse for o caso, você pode usar a opção doNotFake
. Por exemplo, aqui é como você poderia fornecer uma função de simulação personalizada para performance.mark()
em um ambiente jsdom:
/**
* @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);
});