[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


<<:  企业专有资料进行分类的最佳角色- 资料管家(Data Steward)

>>:  《赖田捕手:番外篇》第 40 天:用 Netlify 整合前後端服务

[Day21] 回呼函式 Callback Function

先来看看 MDN 的定义。 回呼函式(callback function)是指能藉由 argumen...

GitHub Action Automation - 自动化你的管理程序与使用第三方 Action

过去的我,一提到 GitHub Action 就直接联想到持续整合与布署,然後就开始进入如何设计、撰...

Day17 - 安装自己开发的套件

昨日完成了爬虫功能开发,今天会将此功能打包成一个套件,并使用pip安装到虚拟环境上。 套件架构 要让...

电脑维修常见问题

电脑维修常见问题 一、电脑问题如何检查? 电脑故障或电脑维修问题可以分为二大类:软件或硬件。 软件故...

[DAY 15] Route 53 part 2

Alias Records 一种 record 类型, 让你将流量路由到 AWS resource...