Timer Mocks
原生的计时函数(如setTimeout
、setInterval
、clearTimeout
、clearInterval
)不是很理想,因为它们需要实际跑那么长时间。 Jest can swap out timers with functions that allow you to control the passage of time. 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); });
这里我们使用了伪计时器jest.useFakeTimers();
。它模拟了setTimeout,并且用模拟函数跑了其他计时函数。
Run All Timers #
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:
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); });
Run Pending Timers #
There are also scenarios where you might have a recursive timer -- that is a timer that sets a new timer in its own callback. For these, running all the timers would be an endless loop… so something like jest.runAllTimers()
is not desirable. For these cases you might use 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); }); });
Run Timers to Time #
Another possibility is use jest.runTimersToTime(msToRun)
. When this API is called, all pending "macro-tasks" that have been queued via setTimeout() or setInterval(), and would be executed within msToRun milliseconds, will be executed. Additionally if those macro-tasks schedule new macro-tasks that would be executed within the same time frame, those will be executed until there are no more macro-tasks remaining in the queue that should be run within msToRun milliseconds.
// 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); });
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()
.
The code for this example is available at examples/timer.