Hone logo
Hone
Problems

Mocking Timers in Jest for Asynchronous Testing

Testing asynchronous code, particularly code that relies on setTimeout, setInterval, or requestAnimationFrame, can be tricky. Jest provides powerful timer mocking capabilities to control and simulate time progression, allowing you to test asynchronous functions in a predictable and deterministic manner. This challenge will guide you through creating and using mock timers in Jest to effectively test code that uses JavaScript's built-in timer functions.

Problem Description

You are tasked with creating a utility function delay that takes a callback function and a delay time (in milliseconds) as arguments. The delay function should execute the callback after the specified delay. Your goal is to write a Jest test suite that effectively tests the delay function using Jest's timer mocking features. You need to mock the setTimeout function to control the passage of time and assert that the callback is executed after the expected delay.

Key Requirements:

  • Create a delay function that uses setTimeout to execute a callback after a specified delay.
  • Use jest.useFakeTimers() to mock the timer functions.
  • Use jest.advanceTimersByTime() to advance the simulated time.
  • Use jest.runAllTimers() to execute any pending timers.
  • Assert that the callback function is called with the expected arguments.
  • Handle edge cases where the delay is zero.

Expected Behavior:

The test suite should:

  1. Verify that the callback is not immediately called when delay is invoked.
  2. Verify that the callback is called after the specified delay when jest.advanceTimersByTime() is used.
  3. Verify that jest.runAllTimers() executes any pending timers.
  4. Handle the case where the delay is zero correctly (the callback should be executed immediately).

Edge Cases to Consider:

  • Delay of 0 milliseconds.
  • Multiple timers scheduled.
  • Callbacks with arguments.

Examples

Example 1:

Input: delay(() => { console.log("Delayed!"); }, 1000);
Output: (After 1 second) "Delayed!" logged to console.
Explanation: The delay function schedules the callback to be executed after 1000ms.  The test will advance the timer by 1000ms and then run all timers to trigger the callback.

Example 2:

Input: delay(() => { console.log("Immediate!"); }, 0);
Output: (Immediately) "Immediate!" logged to console.
Explanation:  A delay of 0ms should execute the callback immediately.

Example 3: (Multiple timers)

Input:
delay(() => { console.log("Timer 1"); }, 500);
delay(() => { console.log("Timer 2"); }, 1000);
Output: (After 1 second) "Timer 1" then "Timer 2" logged to console.
Explanation:  Both timers are scheduled.  Advancing the timer by 1000ms will execute both timers.

Constraints

  • The delay function must use setTimeout.
  • The test suite must use jest.useFakeTimers(), jest.advanceTimersByTime(), and jest.runAllTimers().
  • The test suite must cover the edge case of a 0ms delay.
  • The test suite should be concise and readable.
  • The delay time is always a non-negative integer.

Notes

  • Remember to call jest.useFakeTimers() before any timers are created in your tests.
  • jest.advanceTimersByTime(ms) advances the simulated time by ms milliseconds.
  • jest.runAllTimers() executes all pending timers.
  • Consider using toHaveBeenCalledWith to assert that the callback is called with the correct arguments.
  • Think about how to structure your tests to ensure that the callback is executed after the specified delay. Don't just check if it eventually runs.
Loading editor...
typescript