w3resource

Using Timer Mocks in Jest


The native timer functions (i.e., setTimeout, setInterval, clearTimeout, clearInterval) are less than ideal for use as a testing environment because they depend on real time to elapse. Jest can swap out timers with functions that will allow you to control the passage of time.

// 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('will wait 1 second before ending the game', () => {
  const timerGame = require('../timerGame');
  timerGame();

  expect(setTimeout).toHaveBeenCalledTimes(1);
  expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), 1000);
});

In this case we enable fake timers by calling jest.useFakeTimers();. This will mock out setTimeout and other timer functions using mock functions. If you are running multiple tests inside of one file or describe block, you can call jest.useFakeTimers(); manually before each test or by using a setup function such as beforeEach. If you don?t do so, it will result in the internal usage counter not being reset.

Run All Timers

Another test that you might want to write for this module is one that will assert that the callback is called after 1 second. for you to do this, you have to use Jest's timer control APIs to fast-forward time right in the middle of the test:

test('will call the callback after 1 second', () => {
  const timerGame = require('../timerGame');
  const callback = jest.fn();

  timerGame(callback);

  // At this point, the callback would not have been called yet
  expect(callback).not.toBeCalled();

  // Fast-forwards until all timers have been executed
  jest.runAllTimers();

  // Now our callback would have been called!
  expect(callback).toBeCalled();
  expect(callback).toHaveBeenCalledTimes(1);
});

Run Pending Timers

There can also scenarios where you might have a recursive timer -- that is a timer that will set a new timer in its own callback. For these scenarios, it would be an endless to run all the timers ? so something like jest.runAllTimers() will not be desirable. In these cases you might use 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();

    // Schedules 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);

    //this will Fast forward and exhaust only currently pending timers
    // (but not any new timers that gets created during that process)
    jest.runOnlyPendingTimers();

    // At this point, our 1-second timer would have fired it's callback
    expect(callback).toBeCalled();

    // And it would have created a new timer to start the game over in
    // 10 seconds
    expect(setTimeout).toHaveBeenCalledTimes(2);
    expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), 10000);
  });
});

Advance Timers by Time

This was renamed from runTimersToTime to advanceTimersByTime in Jest 22.0.0

Another possibility is to use jest.advanceTimersByTime(msToRun). When you call this API, all timers will be advanced by msToRun milliseconds. All the pending "macro-tasks" that have been queued via setTimeout() or setInterval(), and that should be executed during this time frame, are executed. Additionally, if those macro-tasks schedule new macro-tasks that would be executed within the same time frame, those are executed until there are no more macro-tasks remaining in the queue that should be run within msToRun milliseconds.

// 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, the callback would not have been called yet
  expect(callback).not.toBeCalled();

  // Fast-forwards until all timers have been executed
  jest.advanceTimersByTime(1000);

  // Now our callback would have been called!
  expect(callback).toBeCalled();
  expect(callback).toHaveBeenCalledTimes(1);
});

Lastly, it can occasionally be useful in some tests to be able to clear all of the pending timers. For this, you can use jest.clearAllTimers().

Previous: Manual Mocks in Jest.
Next: Testing Async Functions in Jest.



Become a Patron!

Follow us on Facebook and Twitter for latest update.

It will be nice if you may share this link in any developer community or anywhere else, from where other developers may find this content. Thanks.

https://www.w3resource.com/jest/timer-mocks-in-jest.php