在我們撰寫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不會重置。
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的功能。
若你是使用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
jest.runOnluPendingTimers()
將這個一秒的timeout加速。我們也可以使用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。
在某些時候我們會需要在測試結束後清除所有的timers,所以jest也提供了jest.clearAllTimers()
的API。
參考文獻:
jest timer-mocks