Timer Mocks
As funções timer nativas (por exemplo, setTimeout
, setInterval
, clearTimeout
, clearInterval
) não são ideais para um ambiente de teste, pois dependem de tempo real para decorrer. Jest pode trocar temporizadores por funções que permitem controlar a passagem do tempo. Great Scott!
// timerGame.js 'use strict'; function timerGame(callback) { console.log('Ready....go!'); setTimeout(() => { console.log('Times up -- stop!'); callback && callback(); }, 1000); } module.exports = timerGame;
// __tests__/timerGame-test.js 'use strict'; jest.useFakeTimers(); test('waits 1 second before ending the game', () => { const timerGame = require('../timerGame'); timerGame(); expect(setTimeout.mock.calls.length).toBe(1); expect(setTimeout.mock.calls[0][1]).toBe(1000); });
Aqui nós habilitamos temporizadores falsos chamando jest.useFakeTimers();
. Isto simula (mocks, em inglês) setTimeout e outras funções de temporizador com funções de simulação.
Executar todos os temporizadores #
Outro teste que podemos querer escrever para este módulo é aquele que afirma que o "callback" é chamado depois de 1 segundo. Para fazer isso, nós vamos usar as APIs de controle de timer do Jest para avançar rapidamente o tempo durante o teste:
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.toBeCalled(); // Fast-forward until all timers have been executed jest.runAllTimers(); // Now our callback should have been called! expect(callback).toBeCalled(); expect(callback.mock.calls.length).toBe(1); });
Executar Temporizadores Pendentes #
Também existem cenários onde você pode ter um temporizador recursivo -- que é um temporizador que define um novo temporizador em seu próprio "callback". Para estes, executar todos os temporizadores resultaria em um loop infinito... então algo como jest.runAllTimers()
não é desejável. Para esses casos, você pode usar jest.runOnlyPendingTimers()
:
// infiniteTimerGame.js 'use strict'; function infiniteTimerGame(callback) { console.log('Ready....go!'); setTimeout(() => { console.log('Times 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 'use strict'; jest.useFakeTimers(); 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.mock.calls.length).toBe(1); expect(setTimeout.mock.calls[0][1]).toBe(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 it's callback expect(callback).toBeCalled(); // And it should have created a new timer to start the game over in // 10 seconds expect(setTimeout.mock.calls.length).toBe(2); expect(setTimeout.mock.calls[1][1]).toBe(10000); }); });
Executar Temporizadores para Tempo #
Outra possibilidade é usar o jest.runTimersToTime(msToRun)
. Quando esta API é chamada, todas as "macro-tarefas" pendentes que foram enfileiradas via setTimeout() ou setInterval(), e que seriam executadas dentro de msToRun milissegundos, serão executadas. Além disso se essas macro-tarefas agendam novas macro-tarefas que seriam executadas dentro do mesmo prazo, aquelas serão executadas até que não haja mais nenhuma macro-tarefa restante na fila que deve ser executada dentro de msToRun milissegundos.
// timerGame.js 'use strict'; function timerGame(callback) { console.log('Ready....go!'); setTimeout(() => { console.log('Times up -- stop!'); callback && callback(); }, 1000); } module.exports = timerGame;
it('calls the callback after 1 second via runTimersToTime', () => { 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.toBeCalled(); // Fast-forward until all timers have been executed jest.runTimersToTime(1000); // Now our callback should have been called! expect(callback).toBeCalled(); expect(callback.mock.calls.length).toBe(1); });
Por último, ocasionalmente pode ser útil em alguns testes ser capaz de limpar todos os temporizadores pendentes. Para isso, temos jest.clearAllTimers()
.
O código para esse exemplo está disponível em examples/timer.