Simulação de Temporizadores
Esta página foi traduzida por PageTurner AI (beta). Não é oficialmente endossada pelo projeto. Encontrou um erro? Reportar problema →
As funções nativas de temporizador (como setTimeout(), setInterval(), clearTimeout(), clearInterval()) não são ideais para ambientes de teste porque dependem da passagem de tempo real. O Jest pode substituir esses temporizadores por funções que permitem controlar o fluxo do tempo. Great Scott!
Veja também a documentação da API de Temporizadores Falsos.
Ativar Temporizadores Falsos
No exemplo a seguir, ativamos temporizadores falsos chamando jest.useFakeTimers(). Isso substitui a implementação original do setTimeout() e outras funções de temporizador. Os temporizadores podem ser restaurados ao comportamento normal com jest.useRealTimers().
function timerGame(callback) {
console.log('Ready....go!');
setTimeout(() => {
console.log("Time's up -- stop!");
callback && callback();
}, 1000);
}
module.exports = timerGame;
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
Outro teste que podemos criar para este módulo verifica se o callback é chamado após 1 segundo. Para isso, usaremos as APIs de controle de temporizador do Jest para avançar rapidamente o tempo durante o teste:
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);
});
Executar Temporizadores Pendentes
Há cenários com temporizadores recursivos – onde um temporizador configura outro temporizador em seu próprio callback. Nesses casos, executar todos os temporizadores criaria um loop infinito, gerando o erro: "Abortando após executar 100000 temporizadores, presumindo loop infinito!"
Se for 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);
// 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);
});
});
Para depuração ou outros motivos, você pode alterar o limite de temporizadores executados antes do erro:
jest.useFakeTimers({timerLimit: 100});
Avançar Temporizadores por Tempo
Outra opção é usar jest.advanceTimersByTime(msToRun). Quando chamada, essa API avança todos os temporizadores em msToRun milissegundos. Todas as "macro-tarefas" pendentes enfileiradas via setTimeout() ou setInterval() serão executadas. Se essas macro-tarefas agendarem novas que devam executar no mesmo período, elas também serão processadas até que a fila esteja vazia.
function timerGame(callback) {
console.log('Ready....go!');
setTimeout(() => {
console.log("Time's up -- stop!");
callback && callback();
}, 1000);
}
module.exports = timerGame;
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);
});
Por fim, em alguns testes pode ser útil limpar todos os temporizadores pendentes. Para isso, temos jest.clearAllTimers().
Simulação Seletiva
Às vezes seu código precisa evitar substituir implementações originais de APIs específicas. Nesses casos, use a opção doNotFake. Por exemplo, veja como fornecer uma função mock personalizada para performance.mark() em 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);
});