Skip to main content

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 waitFor without 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:

  1. All timers (including those in userEvent, waitFor, and component code) use fake timers
  2. waitFor polls using fake timers, so it's fast
  3. Test runs deterministically and quickly
  4. 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 waitFor calls
  • 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 waitFor after advancing timers? Consider if it's necessary.
  • Check: Are there any real timers still running? Look for setTimeout/setInterval outside 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, waitFor uses real timers.
  • Check: Do you need to advance timers more? Some operations may need additional time.

Summary

  1. Enable fake timers early: Before rendering, before user interactions, before waitFor
  2. Use shouldAdvanceTime: true: When you need to advance already-scheduled timers
  3. Always clean up: Call vi.useRealTimers() at the end of each test
  4. Consider skipping waitFor: After advancing timers, if state updates are synchronous

Following these patterns ensures your tests are fast, deterministic, and reliable.