跳转至主内容
Version: 27.0

计时器模拟

原生的定时器函数(如:setTimeout, setInterval, clearTimeout, clearInterval)并不是很方便测试,因为程序需要等待相应的延时。 Jest can swap out timers with functions that allow you to control the passage of time. Great Scott! Great Scott!

// 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();
jest.spyOn(global, 'setTimeout');
test('waits 1 second before ending the game', () => {
const timerGame = require('../timerGame');
timerGame();
expect(setTimeout).toHaveBeenCalledTimes(1);
expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), 1000);
});

Here we enable fake timers by calling jest.useFakeTimers(). This mocks out setTimeout and other timer functions with mock functions. Timers can be restored to their normal behavior with jest.useRealTimers(). This mocks out setTimeout and other timer functions with mock functions. Timers can be restored to their normal behavior with jest.useRealTimers().

While you can call jest.useFakeTimers() or jest.useRealTimers() from anywhere (top level, inside an it block, etc.), it is a global operation and will affect other tests within the same file. Additionally, you need to call jest.useFakeTimers() to reset internal counters before each test. If you plan to not use fake timers in all your tests, you will want to clean up manually, as otherwise the faked timers will leak across tests: Additionally, you need to call jest.useFakeTimers() to reset internal counters before each test. If you plan to not use fake timers in all your tests, you will want to clean up manually, as otherwise the faked timers will leak across tests:

afterEach(() => {
jest.useRealTimers();
});
test('do something with fake timers', () => {
jest.useFakeTimers();
// ...
});
test('do something with real timers', () => {
// ...
});
});
test('do something with real timers', () => {
// ...
});

Run All Timers#

对于这个模块我们还需要写一个测试,用于判断回调函数是否在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);
});

Run Pending Timers#

在某些场景下你可能还需要“循环定时器”——在定时器的callback函数中再次设置一个新定时器。 对于这种情况,如果将定时器一直运行下去那将陷入死循环,所以在此场景下不应该使用jest.runAllTimers() 对于这种情况,如果将定时器一直运行下去那将陷入死循环,所以在此场景下不应该使用jest.runAllTimers() For these cases you might use jest.runOnlyPendingTimers():

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);
});
// __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);
});
});
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);
});
});

Advance Timers by Time#

另一种可选方式是使用 jeste. advancertimersbytime (msToRun)。 When this API is called, all timers are advanced by msToRun milliseconds. 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.

// 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()。 For this, we have jest.clearAllTimers().

The code for this example is available at examples/timer.