原生的計(jì)時(shí)器函數(shù)(例如:setTimeout
,setInterval
,clearTimeout
,clearInterval
)對于測試環(huán)境不是很理想岳颇,因?yàn)樗鼈円蕾囉趯?shí)時(shí)的時(shí)間。Jest 允許你使用函數(shù)替換掉我們的計(jì)時(shí)器來控制時(shí)間的流逝颅湘。
// 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();
// 判斷 setTimeout 方法被調(diào)用了一次
expect(setTimeout).toHaveBeenCalledTimes(1);
// 判斷最后一次調(diào)用 setTimeout 給它傳了什么參數(shù)话侧。這里是第一個(gè)參數(shù)是一個(gè)函數(shù),第二個(gè)參數(shù)是 1000
expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), 1000);
});
在這里栅炒,我們通過調(diào)用 jest.useFakeTimers();
來啟動(dòng)偽計(jì)時(shí)器掂摔。這將使用模擬函數(shù)模擬 setTimeout 和其他計(jì)時(shí)器函數(shù)。如果在一個(gè)文件或 describe
塊中運(yùn)行多個(gè)測試赢赊,jest.useFakeTimers();
可以每次測試之前手動(dòng)調(diào)用或者使用 beforeEach
設(shè)置函數(shù)調(diào)用乙漓。不這樣做將導(dǎo)致未重置內(nèi)部使用計(jì)數(shù)器。
運(yùn)行所有定時(shí)器
我們可能想為這個(gè)模塊編寫的另一個(gè)測試是斷言回調(diào)在1秒后被調(diào)用释移。為了做到這一點(diǎn)叭披,在測試的中間,我們可以使用 Jest 的計(jì)時(shí)器控制 API 接口快進(jìn)?時(shí)間。
test('calls the callback after 1 second', () => {
const timerGame = require('../timerGame');
const callback = jest.fn();
timerGame(callback);
// 這個(gè)時(shí)間點(diǎn),回調(diào)函數(shù)還沒有被調(diào)用决乎。因?yàn)榛卣{(diào)函數(shù)是定時(shí)一秒后執(zhí)行
expect(callback).not.toBeCalled();
// 快進(jìn)?肌稻,直到所有計(jì)時(shí)器都執(zhí)行完畢。(定時(shí)時(shí)間的快進(jìn)功能)
jest.runAllTimers();
// 現(xiàn)在回調(diào)函數(shù)應(yīng)該被調(diào)用了
expect(callback).toBeCalled();
expect(callback).toHaveBeenCalledTimes(1);
});
運(yùn)行掛起的計(jì)時(shí)器
還有一個(gè)場景就是你可能遞歸地使用了定時(shí)器 -- 這有一個(gè)定時(shí)器在自己的回調(diào)里面設(shè)置了新的定時(shí)器咙俩。對于這種情況,運(yùn)行所有的定時(shí)器將是一個(gè)無止境的循環(huán)...因此,像 jest.runAllTimers
不再有效误窖。對于這些情況叮盘,可以使用 jest.runOnlyPendingTimers ()
,僅運(yùn)行掛起的計(jì)時(shí)器:
// 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();
// 10秒后安排下一場比賽
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);
// 這個(gè)點(diǎn)霹俺,setTimeout應(yīng)該已經(jīng)被調(diào)用了一次柔吼。
expect(setTimeout).toHaveBeenCalledTimes(1);
expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), 1000);
// 快進(jìn)和用盡僅當(dāng)前掛起的計(jì)時(shí)器
// (但不是在這個(gè)過程中創(chuàng)建的任何新計(jì)時(shí)器)
jest.runOnlyPendingTimers();
// 此時(shí),我們的1秒定時(shí)器應(yīng)該觸發(fā)了它的回調(diào)
expect(callback).toBeCalled();
// 它應(yīng)該創(chuàng)建一個(gè)新的計(jì)時(shí)器來在10秒內(nèi)重新開始游戲
expect(setTimeout).toHaveBeenCalledTimes(2);
expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), 10000);
});
});
最后丙唧,在某些測試中愈魏,清除所有掛起的計(jì)時(shí)器有時(shí)可能很有用。為此想际,我們使用了jest.clearalltimer()
培漏。
參考
.toHaveBeenCalledTimes(number)
使用這個(gè)方法可以確保模擬函數(shù)調(diào)用的確切次數(shù)。
例如沼琉,假設(shè)你有一個(gè) drinkEach(drink, Array<flavor>)
函數(shù)北苟,它接收一個(gè) drink
函數(shù)并將應(yīng)用于傳遞的數(shù)組。你可能想要檢查 drink
函數(shù)確切的調(diào)用次數(shù)打瘪。你可是使用下面這個(gè)測試套件:
test('drinkEach drinks each drink', () => {
const drink = jest.fn();
drinkEach(drink, ['lemon', 'octopus']);
expect(drink).toHaveBeenCalledTimes(2);
});
.toHaveBeenLastCalledWith(arg1, arg2, ...)
如果您有一個(gè)模擬函數(shù)友鼻,您可以使用. toHaveBeenLastCalledWith
來測試最后調(diào)用它的參數(shù)是什么。例如闺骚,假設(shè)您有一個(gè)applytoallflavor (f)
函數(shù)彩扔,它將f
應(yīng)用于許多口味,并且您希望確保在調(diào)用它時(shí)僻爽,它所作用的最后一種口味是“mango”虫碉。你可以寫:
test('applying to all flavors does mango last', () => {
const drink = jest.fn();
applyToAllFlavors(drink);
expect(drink).toHaveBeenLastCalledWith('mango');
});
jest.useFakeTimers()
指示 Jest 使用標(biāo)準(zhǔn)計(jì)時(shí)器函數(shù)( (setTimeout, setInterval, clearTimeout, clearInterval, nextTick, setImmediate and clearImmediate).)的模擬版本。
jest.useRealTimers()
指示Jest使用標(biāo)準(zhǔn)計(jì)時(shí)器函數(shù)的真實(shí)版本胸梆。
jest.runAllTimers()
耗盡所有的宏任務(wù)隊(duì)列(即敦捧,所有由setTimeout()、setInterval()和setImmediate()以及微任務(wù)隊(duì)列(通常通過process.nextTick在node中接口))
調(diào)用此API時(shí)碰镜,將執(zhí)行所有掛起的宏任務(wù)和微任務(wù)兢卵。如果這些任務(wù)本身調(diào)度了新的任務(wù),那么這些任務(wù)將不斷地耗盡绪颖,直到隊(duì)列中沒有剩余的任務(wù)為止秽荤。
這對于在測試期間同步執(zhí)行setTimeouts非常有用,以便同步地?cái)嘌灾挥性趫?zhí)行setTimeout()或setInterval()回調(diào)之后才會(huì)發(fā)生的一些行為柠横。
jest.runOnlyPendingTimers()
只執(zhí)行當(dāng)前掛起的宏任務(wù)(即窃款,只包括到目前為止由setTimeout()或setInterval()排隊(duì)的任務(wù)。如果當(dāng)前掛起的任何宏任務(wù)調(diào)度了新的宏任務(wù)牍氛,那么這個(gè)調(diào)用將不會(huì)執(zhí)行這些新任務(wù)晨继。
這對于以下場景非常有用:正在測試的模塊遞歸地調(diào)度setTimeout(),而該模塊的回調(diào)調(diào)用setTimeout()調(diào)度另一個(gè)setTimeout()(這意味著調(diào)度永不停止)搬俊。在這些場景中紊扬,能夠一次向前運(yùn)行一步是很有用的曲饱。
jest.clearAllTimers()
從計(jì)時(shí)器系統(tǒng)中刪除任何掛起的計(jì)時(shí)器。
這意味著珠月,如果任何計(jì)時(shí)器已經(jīng)被調(diào)度(但是還沒有執(zhí)行),那么它們將被清除楔敌,并且在將來永遠(yuǎn)沒有機(jī)會(huì)執(zhí)行