Jest 计时器模拟
原生的定时器函数(如:setTimeout, setInterval, clearTimeout, clearInterval)并不是很方便测试,因为程序需要等待相应的延时。 Jest可以将计时器和允许你控制时间流逝的函数计进行互换。
// 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);});
在这里我们通过来模拟定时器函数。 通过mock函数可以模拟setTimeout和其他的定时器函数。 如果你需要在一个文件或一个jest.useFakeTimers();describe块中运行多次测试,可以在每次测试前手动添加jest.useFakeTimers();,或者在beforeEach中添加。 如果不这样做的话将导致内部的定时器不被重置。
运行所有计时器
对于这个模块我们还需要写一个测试,用于判断回调函数是否在1秒后被调用的。 为此,我们将使用Jest的定时器控制API,用于在测试中将时间“快进”到正确的时间点。
test('calls the callback after 1 second', () => {const timerGame = require('../timerGame');const callback = jest.fn();timerGame(callback);// 在这个时间点,定时器的回调不应该被执行expect(callback).not.toBeCalled();// “快进”时间使得所有定时器回调被执行jest.runAllTimers();// 现在回调函数应该被调用了!expect(callback).toBeCalled();expect(callback).toHaveBeenCalledTimes(1);});
运行挂起的计时器
在某些场景下你可能还需要“循环定时器”——在定时器的callback函数中再次设置一个新定时器。 对于这种情况,如果将定时器一直运行下去那将陷入死循环,所以在此场景下不应该使用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 secondssetTimeout(() => {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 callbackexpect(callback).toBeCalled();// And it should have created a new timer to start the game over in// 10 secondsexpect(setTimeout).toHaveBeenCalledTimes(2);expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), 10000);});});
按时间提前计时器
从22.0.0版本的Jest开始,runTimersToTime被重命名为advanceTimersByTime。
另一种可选方式是使用 jeste. advancertimersbytime (msToRun)。 当调用此API时,所有计时器都会以msToRun毫秒为单位提前。所有通过setTimeout() 或setInterval() 而处于任务队列中等待中的“宏任务”和一切其他应该在本时间片中被执行的东西都应该被执行。 此外,如果这些宏任务计划在同一时间段内执行的新宏任务,则将执行这些宏任务,直到队列中不再有应在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);// 在这个时间点,回调函数不应该被执行expect(callback).not.toBeCalled();// “快进”时间,使得所有定时器回调都被执行jest.advanceTimersByTime(1000);// 到这里,所有的定时器回调都应该被执行了!expect(callback).toBeCalled();expect(callback).toHaveBeenCalledTimes(1);});
最后,在某些测试中,能够清除所有挂起的计时器有时可能很有用。为此,我们有jest.clearAllTimers().