天天看點

React 16 Jest定時器模拟 Timer MocksReact 16 Jest定時器模拟 Timer Mocks

轉載

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