计时器模拟
原生的定时器函数(如:setTimeout
, setInterval
, clearTimeout
, clearInterval
)并不是很方便测试,因为程序需要等待相应的延时。 Jest可以通过一个函数转换计时器以便允许你控制时间流量。 Great Scott!
'use strict';
function timerGame(callback) {
console.log('Ready....go!');
setTimeout(() => {
console.log("Time's up -- stop!");
callback && callback();
}, 1000);
}
module.exports = timerGame;
'use strict';
jest.useFakeTimers(); // or you can set "timers": "fake" globally in configuration file
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();
来模拟定时器函数。 通过mock函数可以模拟setTimeout和其他的定时器函数。 如果你需要在一个文件或一个describe块中运行多次测试,可以在每次测试前手动添加jest.useFakeTimers();
,或者在beforeEach
中添加。 如果不这样做的话将导致内部的定时器不被重置。
All of the following functions need fake timers to be set, either by jest.useFakeTimers()
or via "timers": "fake"
in the config file.
Currently, two implementations of the fake timers are included - modern
and legacy
, where legacy
is still the default one. See configuration for how to configure it.
运行所有计时器
对于这个模块我们还需要写一个测试,用于判断回调函数是否在1秒后被调用的。 为此,我们将使用Jest的定时器控制API,用于在测试中将时间“快进”到正确的时间点。
jest.useFakeTimers();
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函数中再次设置一个新定时器。 For these, running all the timers would be an endless loop, throwing the following error:
Ran 100000 timers, and there are still more! Assuming we've hit an infinite recursion and bailing out...
So something like jest.runAllTimers()
is not desirable. 对于这种情况,如果将定时器一直运行下去那将陷入死循环,所以在此场景下不应该使用jest.runAllTimers()
For these cases you might use jest.runOnlyPendingTimers()
:
'use strict';
function infiniteTimerGame(callback) {
console.log('Ready....go!');
setTimeout(() => {
console.log("Time's up! expect(callback).toBeCalled();
expect(callback).toHaveBeenCalledTimes(1);
});
'use strict';
jest.useFakeTimers();
jest.spyOn(global, 'setTimeout');
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 its 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);
});
});
使用时间的高级计时器
另一种可选方式是使用 jeste. advancertimersbytime (msToRun)
。 通过调用msToRun
这个 API时, 所有计时器都将以毫秒增长。 When this API is called, all timers are advanced by msToRun
milliseconds. 所有通过setTimeout() 或setInterval() 而处于任务队列中等待中的“宏任务”和一切其他应该在本时间片中被执行的东西都应该被执行。 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.
'use strict';
function timerGame(callback) {
console.log('Ready....go!');
setTimeout(() => {
console.log("Time's up -- stop!");
callback && callback();
}, 1000);
}
module.exports = timerGame;
jest.useFakeTimers();
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 until all timers have been executed
jest.advanceTimersByTime(1000);
// Now our callback should have been called!
expect(callback).toBeCalled();
expect(callback).toHaveBeenCalledTimes(1);
});
最后,在某些测试中你可能需要清除所有等待状态下的定时器,为此,可以使用 jest.clearAllTimers()
。 For this, we have jest.clearAllTimers()
.
The code for this example is available at examples/timer.