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.