Jest - 計(jì)時(shí)器模擬

原生的計(jì)時(shí)器函數(shù)(例如:setTimeoutsetIntervalclearTimeoutclearInterval)對于測試環(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í)行

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末啤挎,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子卵凑,更是在濱河造成了極大的恐慌庆聘,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,525評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件勺卢,死亡現(xiàn)場離奇詭異伙判,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)黑忱,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,203評論 3 395
  • 文/潘曉璐 我一進(jìn)店門宴抚,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人甫煞,你說我怎么就攤上這事菇曲。” “怎么了抚吠?”我有些...
    開封第一講書人閱讀 164,862評論 0 354
  • 文/不壞的土叔 我叫張陵常潮,是天一觀的道長。 經(jīng)常有香客問我楷力,道長喊式,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,728評論 1 294
  • 正文 為了忘掉前任萧朝,我火速辦了婚禮岔留,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘剪勿。我一直安慰自己贸诚,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,743評論 6 392
  • 文/花漫 我一把揭開白布厕吉。 她就那樣靜靜地躺著酱固,像睡著了一般。 火紅的嫁衣襯著肌膚如雪头朱。 梳的紋絲不亂的頭發(fā)上运悲,一...
    開封第一講書人閱讀 51,590評論 1 305
  • 那天,我揣著相機(jī)與錄音项钮,去河邊找鬼班眯。 笑死希停,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的署隘。 我是一名探鬼主播宠能,決...
    沈念sama閱讀 40,330評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼磁餐!你這毒婦竟也來了违崇?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,244評論 0 276
  • 序言:老撾萬榮一對情侶失蹤诊霹,失蹤者是張志新(化名)和其女友劉穎羞延,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體脾还,經(jīng)...
    沈念sama閱讀 45,693評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡伴箩,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,885評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了鄙漏。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片嗤谚。...
    茶點(diǎn)故事閱讀 40,001評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖怔蚌,靈堂內(nèi)的尸體忽然破棺而出呵恢,到底是詐尸還是另有隱情,我是刑警寧澤媚创,帶...
    沈念sama閱讀 35,723評論 5 346
  • 正文 年R本政府宣布渗钉,位于F島的核電站,受9級特大地震影響钞钙,放射性物質(zhì)發(fā)生泄漏鳄橘。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,343評論 3 330
  • 文/蒙蒙 一芒炼、第九天 我趴在偏房一處隱蔽的房頂上張望瘫怜。 院中可真熱鬧,春花似錦本刽、人聲如沸鲸湃。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,919評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽暗挑。三九已至,卻和暖如春斜友,著一層夾襖步出監(jiān)牢的瞬間炸裆,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,042評論 1 270
  • 我被黑心中介騙來泰國打工鲜屏, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留烹看,地道東北人国拇。 一個(gè)月前我還...
    沈念sama閱讀 48,191評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像惯殊,于是被迫代替她去往敵國和親酱吝。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,955評論 2 355

推薦閱讀更多精彩內(nèi)容