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.
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
- Weekly Trends and Language Statistics
- Weekly Trends and Language Statistics