轉載
React 16 Jest定時器模拟 Timer Mocks
項目初始化
git clone https:// github.com/durban89/webpack4-react16-reactrouter-demo.git
cd webpack4-react16-reactrouter-demo
git fetch origin
git checkout v_1.0.26
npm install
定時器模拟(Timer Mocks)
原生定時器功能(即setTimeout,setInterval,clearTimeout,clearInterval)對于測試環境來說不太理想,因為它們依賴于實時時間。
Jest可以将定時器換成允許我們自己控制時間的功能。
示例如下
src/lib/timerGame.js
function timerGame(callback) {
console.log('Ready....go!');
setTimeout(() => {
console.log('Times up -- stop!');
return callback && callback();
}, 1000);
}
module.exports = timerGame;
src/__tests__/jest_timerGame.test.js
const timerGame = require('../lib/timerGame');
jest.useFakeTimers();
test('等待1秒鐘後結束遊戲', () => {
timerGame();
expect(setTimeout).toHaveBeenCalledTimes(1);
expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), 1000);
});
這裡我們通過調用jest.useFakeTimers();來啟用假定時器。
這使用模拟函數模拟了setTimeout和其他計時器函數。
如果在一個檔案或描述塊中運作多個測試,則jest.useFakeTimers();
可以在每次測試之前手動調用,也可以使用諸如beforeEach之類的設定函數調用。
不這樣做會導緻内部使用計數器不被重置。
運作所有計時器(Run All Timers)
為上面的子產品timerGame寫一個測試,這個測試在1秒鐘後調用回調callback,示例如下
const timerGame = require('../lib/timerGame');
jest.useFakeTimers();
test('1秒鐘後調用回調callback', () => {
const callback = jest.fn();
timerGame(callback);
// 在這個時間點上,callback回調函數還沒有被調用
expect(callback).not.toBeCalled();
// 所有timers被執行
jest.runAllTimers();
// 現在我們的callback回調函數被調用
expect(callback).toBeCalled();
expect(callback).toHaveBeenCalledTimes(1);
});
運作待定時間器
在某些情況下,您可能還有一個遞歸計時器 - 這是一個在自己的回調中設定新計時器的計時器。
對于這些,運作所有計時器将是一個無限循環,是以像jest.runAllTimers()這樣的東西是不可取的。
對于這些情況,您可以使用jest.runOnlyPendingTimers()。示例如下
src/lib/infiniteTimerGame.js
function infiniteTimerGame(callback) {
console.log('Ready....go!');
setTimeout(() => {
console.log('Times up! 10 seconds before the next game starts...');
if (callback) {
callback();
}
// 10秒鐘後執行下一個
setTimeout(() => {
infiniteTimerGame(callback);
}, 10000);
}, 1000);
}
module.exports = infiniteTimerGame;
src/__tests__/jest_infiniteTimerGame.test.js
const infiniteTimerGame = require('../lib/infiniteTimerGame');
jest.useFakeTimers();
describe('infiniteTimerGame', () => {
test('schedules a 10-second timer after 1 second', () => {
const callback = jest.fn();
infiniteTimerGame(callback);
// 在這裡,會在意秒鐘後執行callback的回調
expect(setTimeout).toHaveBeenCalledTimes(1);
expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), 1000);
// 隻有目前待定的計時器(但不是在該過程中建立的任何新計時器)
jest.runOnlyPendingTimers();
// 此時,1秒鐘的計時器應該已經被回調了
expect(callback).toBeCalled();
// 它應該建立一個新的計時器,以便在10秒内啟動遊戲
expect(setTimeout).toHaveBeenCalledTimes(2);
expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), 10000);
});
});
按時間提前計時器(Advance Timers by Time)
另一種可能性是使用jest.advanceTimersByTime(msToRun)。
調用此API時,所有計時器都按msToRun毫秒提前。
将執行已經通過setTimeout()或setInterval()排隊并且将在此時間幀期間執行所有待處理"宏任務"。
此外,如果這些宏任務排程将在同一時間幀内執行的新宏任務,那麼将執行這些宏任務,直到隊列中不再有宏任務應該在msToRun毫秒内運作。
const timerGame = require('../lib/timerGame');
jest.useFakeTimers();
it('1秒鐘後通過advanceTimersByTime調用回調函數', () => {
const callback = jest.fn();
timerGame(callback);
// callback還沒有被執行
expect(callback).not.toBeCalled();
// 提前1秒鐘執行
jest.advanceTimersByTime(1000);
// 所有的callback被調用
expect(callback).toBeCalled();
expect(callback).toHaveBeenCalledTimes(1);
});
在某些測試中偶爾可能有用,就是在測試中可以清除所有挂起的計時器。
為此,可以使用jest.clearAllTimers()。
項目實踐位址
https:// github.com/durban89/webpack4-react16-reactrouter-demo.git
tag:v_1.0.27