Run All Timers
Another test for this module that we should write is one that asserts the callback is called after 1 second. To accomplish this, we'll use Jest's timer control APIs to jump ahead in time in the middle of the test:
jest.useFakeTimers();
test('calls the callback after 1 second', () => {
const timerGame = require('../timerGame');
const callback = jest.fn();
timerGame(callback);
// At this point in time, the callback shouldn’t 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);
});
Run Pending Timers
There are also situations where a recursive timer is used, which is a timer that sets a new timer in its own callback. Running all of the timers would result in an infinite loop, resulting in the following error:
Ran 100000 timers, and there are still more! Assuming we've hit an infinite recursion and bailing out…
As a result, something like jest.runAllTimers() isn't a good idea. You could use jest.runOnlyPendingTimers() in these cases:
infiniteTimerGame.js
'use strict';
function infiniteTimerGame(callback) {
console.log('Ready....go!');
setTimeout(() => {
console.log("Time's up!There are 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();
jest.spyOn(global, 'setTimeout');
describe('infiniteTimerGame', () => {
test('schedules a 10 seconds 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 its 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);
});
});
Advance Timers By Time
Use jest.advanceTimersByTime(msToRun) as another option. All timers are advanced by msToRun milliseconds when this API is called. All pending "macro-tasks" that have been queued via setTimeout() or setInterval() and are scheduled to run during this time period will be completed. Furthermore, if those macro-tasks schedule new macro-tasks to run within the same time frame, those will be executed until there are no more macro-tasks 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;
__tests__/timerGame-test.js
jest.useFakeTimers();
it('calls callback after 1 second via advanceTimersByTime', () => {
const timerGame = require('../timerGame');
const callback = jest.fn();
timerGame(callback);
// At this point in time, the callback shouldn’t have been called yet
expect(callback).not.toBeCalled();
// Fast-forward until all timers have been executed
jest.advanceTimersByTime(1000);
// Now our callback should have been called!
expect(callback).toBeCalled();
expect(callback).toHaveBeenCalledTimes(1);
});
Finally, having the ability to clear all pending timers may be useful in some tests. We have jest.clearAllTimers() for this.
FAQs
-
Why do you need to restore the timers after your test runs?
The main reason for this is to avoid coupling 3rd party libraries running after your test finishes (e.g. cleanup functions) to your fake timers and instead use real timers.
-
What do you use to restore the timers?
To restore the timers you usually call useRealTimers in afterEach.
-
Why is it important to also call runOnlyPendingTimers before switching to real timers?
This will ensure that all pending timers are flushed before switching to real timers. If you don't advance the timers and instead use real timers, the scheduled tasks will not be completed, resulting in unexpected behaviour. This is especially important for third parties who schedule tasks without your knowledge.
-
Why do we use fake timers?
The tests may become unpredictable, slow, and flaky if the code does not use fake timers.
-
How can you clear all the pending timers?
It may be useful in some tests to be able to use jest.clearAllTimers() to clear all of the pending timers.
Key Takeaways
In this article, we have extensively discussed the theoretical and practical implementation of Fake Timers.
We hope that this blog has helped you enhance your knowledge regarding the theoretical and practical implementation of Fake Timers and if you would like to learn more, check out our articles on Coding Ninjas Studio. Do upvote our blog to help other ninjas grow. Happy Coding!