Stop Waiting for setTimeout: Mastering Jest Fake Timers
Stop slowing down your CI pipeline with real-time delays. Learn how to use jest.useFakeTimers(), advanceTimersByTime, and runAllTimers to test asynchronous JavaScript without waiting for real delays.
13 Mar 2026, 01:07 UTC

The Cost of Real-Time Testing
\nTesting a function that triggers an action after a 5-second delay usually leads to one of two bad choices: you either add a 5-second await to your test suite, slowing down your CI pipeline, or you reduce the delay to 1ms for the test environment, which creates a discrepancy between your test and production behavior.
The solution is jest.useFakeTimers(). This feature replaces the global timer functions—setTimeout, setInterval, clearTimeout, and clearInterval—with mocks. Instead of waiting for the clock to tick, you tell Jest exactly how much time has passed, making asynchronous time-based logic deterministic and instantaneous.
Controlling the Clock
\nOnce you enable fake timers, Jest intercepts all calls to the timer API. The clock effectively pauses until you manually advance it. This allows you to test edge cases—like a timeout that triggers after exactly 30 seconds—without actually waiting 30 seconds.
\nThere are three primary ways to move the clock forward:
\n- \n
jest.advanceTimersByTime(ms): Moves the clock forward by a specific number of milliseconds. This is the most precise method for testing specific intervals. \n jest.runAllTimers(): Exhausts the timer queue by executing every scheduled task until none remain. Use this with caution, as recursive timers can cause an infinite loop. \n jest.runOnlyPendingTimers(): Executes only the timers that were scheduled at the moment the function was called. This prevents newly scheduled timers from triggering in the same cycle. \n
Worked Example: Testing a Debounce Function
\nConsider a search input that only triggers an API call after the user has stopped typing for 500ms. Testing this with real timers is flaky and slow.
\n\n// search.js\nexport function debounceSearch(callback) {\n let timer;\n return (...args) => {\n clearTimeout(timer);\n timer = setTimeout(() => callback(...args), 500);\n };\n}\n\n// search.test.js\nimport { debounceSearch } from './search';\n\ndescribe('debounceSearch', () => {\n beforeEach(() => {\n jest.useFakeTimers();\n });\n\n afterEach(() => {\n // Critical: restore real timers to avoid leaking state to other tests\n jest.useRealTimers();\n });\n\n it('should only call the callback after 500ms of inactivity', () => {\n const callback = jest.fn();\n const debounced = debounceSearch(callback);\n\n debounced('query 1');\n debounced('query 2');\n\n // At this point, the callback should not have been called yet\n expect(callback).not.toHaveBeenCalled();\n\n // Fast-forward 499ms - still shouldn't be called\n jest.advanceTimersByTime(499);\n expect(callback).not.toHaveBeenCalled();\n\n // Fast-forward the final 1ms\n jest.advanceTimersByTime(1);\n expect(callback).toHaveBeenCalledTimes(1);\n expect(callback).toHaveBeenCalledWith('query 2');\n });\n});\n\n\nExecution Details
\nRun this test using the command npm test or npx jest search.test.js. Ensure you have the jest package installed in your project. Because the timers are mocked, the test will complete in milliseconds regardless of the 500ms delay defined in the code.
Critical Limitations and Pitfalls
\nFake timers are powerful, but they do not mock everything related to time. A common mistake is assuming jest.useFakeTimers() affects Date.now() or new Date(). It does not. If your logic depends on the current timestamp, you must use jest.setSystemTime(date) to lock the system clock to a specific moment.
Additionally, be wary of mixing fake timers with real asynchronous operations, such as fetch requests. While Jest mocks the timer, it does not mock the network latency. If a setTimeout is wrapped inside a Promise that is waiting for a real network response, advanceTimersByTime may trigger the timer, but the Promise might still be pending, leading to a hanging test or a failed assertion.
Verification and Cleanup
\nTo verify that fake timers are working, attempt to run a test with a 10-second setTimeout without calling advanceTimersByTime. The test should either time out or fail the assertion immediately, proving the clock is paused.
Always include jest.useRealTimers() in your afterEach block. If you omit this, subsequent tests in the same file (or other files in some configurations) will inherit the mocked clock, causing unexpected behavior in tests that rely on actual time passage or third-party libraries that use timers internally.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.