Skip to main content
Versão: 29.5

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!

informação

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().

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

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:

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

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

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.

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

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