Vitest Timers Mocking
A guide to using fake timers correctly in Vitest tests to avoid performance issues and flaky tests.
Why Use Fake Timers?
Fake timers allow you to control time in your tests, making them:
- Faster: Skip waiting for real time delays
- Deterministic: Tests run consistently regardless of system load
- Reliable: No race conditions from timing variations
The Problem: Setting Fake Timers Too Late
Common mistakes when testing timers in Vitest:
- Enabling fake timers too late
- Using
waitForwithout understanding how it works - Not cleaning up fake timers after the test
This makes tests slow and flaky.
Incorrect Pattern 1: Too Late + Missing shouldAdvanceTime
render(<Component />);
await userEvent.click(button);
// Too late + shouldAdvanceTime not used
vi.useFakeTimers();
vi.advanceTimersByTime(5000);
// Fails
/* assertion */
Incorrect Pattern 2: Too Late + waitFor Uses Real Timers
render(<Component />);
await userEvent.click(button);
// Too late
vi.useFakeTimers({ shouldAdvanceTime: true });
vi.advanceTimersByTime(5000);
// waitFor uses real timers internally → 1000ms+ delay
await waitFor(() => { /* assertion */ });
Note: The test will pass, but will be slow. waitFor continues to use real timers for polling, even after advanceTimersByTime. Result: unnecessary 1000ms+ delays instead of instant checks.
Performance Impact: Tests can take 10-15x longer than necessary (e.g., 87ms → 1300ms).
Incorrect Pattern 3: Missing Cleanup
vi.useFakeTimers({ shouldAdvanceTime: true });
render(<Component />);
await userEvent.click(button);
act(() => vi.advanceTimersByTime(5000));
/* assertion */
// Missing cleanup of fake timers
Correct Pattern
Solution: Enable fake timers before any component rendering or async operations.
vi.useFakeTimers({ shouldAdvanceTime: true });
render(<Component />);
await userEvent.click(button);
act(() => vi.advanceTimersByTime(5000));
/* assertion */
vi.useRealTimers(); // cleanup
Why This Works:
- All timers (including those in
userEvent,waitFor, and component code) use fake timers waitForpolls using fake timers, so it's fast- Test runs deterministically and quickly
- No race conditions from real-time delays
Best Practices
1. Enable Fake Timers Early
Always enable fake timers before:
- Component rendering
- User interactions (
userEvent) - Any
waitForcalls - Any async operations that might use timers
// Enable at the start
vi.useFakeTimers();
// Then proceed with test setup
render(<Component />);
await userEvent.click(button);
// Advance timers
act(() => {
vi.advanceTimersByTime(5000);
});
// Assertions
/* assertion */
// Clean up
vi.useRealTimers();
2. Use shouldAdvanceTime: true When Needed
Use shouldAdvanceTime: true when you need to advance timers that are already scheduled:
// For setTimeout/setInterval that are already running
vi.useFakeTimers({ shouldAdvanceTime: true });
render(<Component />);
await userEvent.click(button);
// Advance time
act(() => {
vi.advanceTimersByTime(5000);
});
// Assertions
/* assertion */
// Clean up
vi.useRealTimers();
3. Always Clean Up
Always restore real timers at the end of your test to avoid affecting other tests:
vi.useFakeTimers();
render(<Component />);
await userEvent.click(button);
// Advance timers
act(() => {
vi.advanceTimersByTime(5000);
});
// Assertions
/* assertion */
// Always restore
vi.useRealTimers();
4. Alternative: Skip waitFor After Advancing Timers
If you've already advanced timers and the state update is synchronous, you can skip waitFor:
vi.useFakeTimers({ shouldAdvanceTime: true });
render(<Component />);
await userEvent.click(button);
await waitFor(() => { /* assertion */ });
// Advance timers
act(() => {
vi.advanceTimersByTime(5000);
});
// State should be updated synchronously after advancing timers
// No need for waitFor if the update is immediate
/* assertion */
vi.useRealTimers();
Note: This only works if the state update is synchronous. If there are async operations triggered by the timeout, you still need waitFor.
Common Scenarios
Scenario 1: Testing Component with setTimeout
// Component code
useEffect(() => {
if (isSuccess) {
const timeoutId = setTimeout(() => {
resetState();
}, 5000);
return () => clearTimeout(timeoutId);
}
}, [isSuccess, resetState]);
// Correct test
vi.useFakeTimers({ shouldAdvanceTime: true });
render(<Component />);
await userEvent.click(button);
await waitFor(() => { /* assertion */ });
act(() => {
vi.advanceTimersByTime(5000);
});
await waitFor(() => { /* assertion */ });
vi.useRealTimers();
Scenario 2: Testing Debounced Input
// Correct test
vi.useFakeTimers();
render(<SearchInput />);
const input = screen.getByRole('textbox');
await userEvent.type(input, 'test query');
// Search should not trigger immediately
/* assertion */
// Advance past debounce delay (e.g., 300ms)
act(() => {
vi.advanceTimersByTime(300);
});
await waitFor(() => { /* assertion */ });
vi.useRealTimers();
Scenario 3: Testing Polling Behavior
// Correct test
vi.useFakeTimers({ shouldAdvanceTime: true });
render(<PollingComponent />);
// Initial render
/* assertion */
// Advance 10 seconds - should trigger poll
act(() => {
vi.advanceTimersByTime(10000);
});
await waitFor(() => { /* assertion */ });
vi.useRealTimers();
Troubleshooting
Test is Still Slow
- Check: Are fake timers enabled before rendering?
- Check: Are you using
waitForafter advancing timers? Consider if it's necessary. - Check: Are there any real timers still running? Look for
setTimeout/setIntervaloutside of fake timer control.
Test is Flaky
- Check: Are fake timers properly cleaned up with
vi.useRealTimers()? - Check: Are you advancing timers enough? Some operations may need more time.
- Check: Are there race conditions between fake timers and real async operations?
waitFor Times Out
- Check: Is the state actually updating? The timeout might be correct.
- Check: Are fake timers enabled before
waitFor? If not,waitForuses real timers. - Check: Do you need to advance timers more? Some operations may need additional time.
Summary
- Enable fake timers early: Before rendering, before user interactions, before
waitFor - Use
shouldAdvanceTime: true: When you need to advance already-scheduled timers - Always clean up: Call
vi.useRealTimers()at the end of each test - Consider skipping
waitFor: After advancing timers, if state updates are synchronous
Following these patterns ensures your tests are fast, deterministic, and reliable.