前言
在我们撰写jest的时候,常常会遇到source code的function有使用到setTimeout的情况,若是我们在jest中也使用setTimeout来模拟source code的真实timeout情况,那么一旦jest的数量变多时就会花费很多时间在等待timeout,所以jest就提供了fakeTimers
的功能,他可以让使用者透过这个function达到自由控制时间(timer时间)的功能。
// timerGame.js'use strict';function timerGame(callback) { console.log('Ready....go!'); setTimeout(() => { console.log("Time's 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).toHaveBeenCalledTimes(1); expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), 1000);});
使用jest.useFakeTimers()
来开启fake timers功能,他模拟了setTimeout或其他timer的功能,若是在同一个jest中使用了多个fake timers则需要在beforeEach中再次设定jest.useFakeTimers(),否则timer不会重置。
Run All Timers
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).toHaveBeenCalledTimes(1);});
以上面的例子来说,若我们需要若我们需要在一秒后呼叫这个callback function,所以我们使用jest的API(jest.runAllTimers())来加快timer的时间。
在还没使用jest.runAllTimers()
时,callback function因为还在处于setTimeout阶段所以还没被执行,而当执行了jest的API那他就会将所有timer加快让他提前完成,所以下一行就可以看到callback function已经提前被呼叫了,这就是jest fake timer的功能。
Run Pending Timers
若你是使用resursive timer
,因为他是在callback function中再设定一个timer,所以并不适合使用jest.runAllTimers()
,这种情况可以使用jest.runOnlyPendingTimers()
。
// infiniteTimerGame.js'use strict';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'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).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 it's callback expect(callback).toBeCalled(); /* 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); });});
由于source code中的function递迴的呼叫自身,并在自身中在建立一个setTimeout,所以我们在jest中呼叫这个function
刚进入这个function,他里面的setTimeout只有被呼叫一次(因为正在一秒的timeout阶段,所以还没呼叫10秒的setTimeout)。我们使用jest.runOnluPendingTimers()
将这个一秒的timeout加速。因为加速了timer,所以可以马上呼叫第二的timer(10秒的setTimeout)。Advance Timers by Time
我们也可以使用jest.advanceTimersByTime(msToRun)
来指定需要加速多少毫秒。
// timerGame.js'use strict';function timerGame(callback) { console.log('Ready....go!'); setTimeout(() => { console.log("Time's up -- stop!"); callback && callback(); }, 1000);}module.exports = timerGame;
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.toBeCalled(); // Fast-forward 1000ms jest.advanceTimersByTime(1000); // Now our callback should have been called! expect(callback).toBeCalled(); expect(callback).toHaveBeenCalledTimes(1);});
由于我们的source code中的timer设定为等待1秒,所以们可以指定将timer加速1s。
Clear Timers
在某些时候我们会需要在测试结束后清除所有的timers,所以jest也提供了jest.clearAllTimers()
的API。
参考文献:
jest timer-mocks