iT邦幫忙

0

[jest] Guides - Timer Mocks

前言

在我們撰寫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不會重置。


Run All Timers

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的功能。


Run Pending Timers

若你是使用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

  1. 剛進入這個function,他裡面的setTimeout只有被呼叫一次(因為正在一秒的timeout階段,所以還沒呼叫10秒的setTimeout)。
  2. 我們使用jest.runOnluPendingTimers()將這個一秒的timeout加速。
  3. 因為加速了timer,所以可以馬上呼叫第二的timer(10秒的setTimeout)。

Advance Timers by Time

我們也可以使用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。


Clear Timers

在某些時候我們會需要在測試結束後清除所有的timers,所以jest也提供了jest.clearAllTimers()的API。

參考文獻:
jest timer-mocks


尚未有邦友留言

立即登入留言