Mastering Time with Jest's useFakeTimers
and donotfake
Jest's useFakeTimers
is a powerful tool for testing asynchronous code that relies on timers, such as setTimeout
, setInterval
, and Date.now
. It allows you to control the passage of time within your tests, making them more predictable and reliable. However, sometimes you might want to specifically exclude certain timers from being faked. This is where the donotfake
option comes in handy.
Let's consider a scenario where you have a function that schedules a task using setTimeout
but also relies on the actual current time for some internal logic:
function scheduleTask(delay) {
const startTime = Date.now(); // Get current time
setTimeout(() => {
const elapsedTime = Date.now() - startTime;
console.log(`Task completed after ${elapsedTime} milliseconds`);
}, delay);
}
Now, if we directly use jest.useFakeTimers()
in our test without any modifications, the Date.now()
calls inside the function will be mocked, leading to unexpected results:
test('scheduleTask with fake timers', () => {
jest.useFakeTimers();
scheduleTask(1000);
// Advance the fake timer
jest.advanceTimersByTime(1000);
// This will always log "Task completed after 0 milliseconds"
});
To fix this, we can use the donotfake
option within useFakeTimers
to exclude Date.now
from being mocked:
test('scheduleTask with fake timers and donotfake', () => {
jest.useFakeTimers({ doNotFake: ['Date.now'] });
scheduleTask(1000);
jest.advanceTimersByTime(1000);
// This will log "Task completed after 1000 milliseconds" as expected
});
By adding doNotFake: ['Date.now']
, we ensure that the Date.now
function remains untouched, allowing the actual time to be used within our test.
Understanding donotfake
:
The donotfake
option is a powerful feature that allows you to fine-tune Jest's fake timers behavior. It accepts an array of strings representing the functions or methods you want to exclude from being mocked. This can be especially helpful when dealing with complex code that interacts with multiple timers and external dependencies.
Practical Example:
Imagine you have a function that triggers an API call using setTimeout
after a certain delay but also needs to check the current time to ensure it's within a specific time window. Here's how you can test this function effectively using donotfake
:
function makeAPICall(delay) {
const now = Date.now();
setTimeout(() => {
if (now + delay < new Date().getTime()) {
// Make API call only if still within the time window
console.log('Making API call');
} else {
console.log('Time window expired');
}
}, delay);
}
test('makeAPICall with fake timers and donotfake', () => {
jest.useFakeTimers({ doNotFake: ['Date.now'] });
const now = Date.now();
makeAPICall(1000);
jest.advanceTimersByTime(500);
expect(now + 500).toBeLessThan(new Date().getTime());
jest.advanceTimersByTime(500);
expect(now + 1000).toBeGreaterThanOrEqual(new Date().getTime());
});
In this example, we explicitly exclude Date.now
from being mocked using donotfake
. This ensures that the actual time is used for the time window checks within the makeAPICall
function, allowing for accurate testing of the logic.
Key Points:
- Use
donotfake
to exclude specific timer functions from being mocked when usingjest.useFakeTimers()
. - Carefully consider which functions need to be mocked and which need to be left untouched for your tests to be accurate and meaningful.
donotfake
provides fine-grained control over Jest's fake timers behavior, allowing you to tailor them to your specific testing needs.
By understanding and leveraging the power of useFakeTimers
and donotfake
, you can write more effective and reliable tests for your asynchronous code.