Skip to content

feat: automatically advance fake timers in Jest and Vitest - #1324

Draft
TrevorBurnham wants to merge 1 commit into
testing-library:mainfrom
TrevorBurnham:fix-fake-timers-clock-detection
Draft

feat: automatically advance fake timers in Jest and Vitest#1324
TrevorBurnham wants to merge 1 commit into
testing-library:mainfrom
TrevorBurnham:fix-fake-timers-clock-detection

Conversation

@TrevorBurnham

@TrevorBurnham TrevorBurnham commented Aug 15, 2026

Copy link
Copy Markdown

Alternative to #1304. Same goal: userEvent should advance fake timers without advanceTimers being configured manually. Different detection mechanism, because detecting the jest/vi globals doesn't hold up.

Fixes #1115

Why not detect framework globals

#1304 reads globalThis.jest and globalThis.vi. Verified against Vitest 4.1.10 and Jest 30.4.1:

  • Jest injects jest into each module's scope, not onto globalThis. Inside a test file typeof jest === 'object' while globalThis.jest is undefined, so the Jest branch never fires.
  • globalThis.vi only exists with globals: true, which defaults to false. Users who import {vi} from 'vitest' get no detection.
  • With globals: true and real timers it is worse than inert. advanceTimers runs on every API call, and vi.advanceTimersByTime() throws when timers are not mocked, so await user.click(button) fails with A function to advance timers was called but the timers APIs are not mocked.

What this does instead

Jest's modern fake timers and Vitest's fake timers are both built on @sinonjs/fake-timers, which exposes the installed clock on each timer function it replaces. So tick that clock when it is present:

export function advanceFakeTimers(delay: number): void {
  const {clock} = globalThis.setTimeout as typeof globalThis.setTimeout & {
    clock?: FakeClock
  }

  if (typeof clock?.tick === 'function') {
    clock.tick(delay)
  }
}

This works however the framework is imported, and only while fake timers are installed, so real timers are never touched. It is the same signal @testing-library/dom uses in jestFakeTimersAreEnabled().

Detection happens per wait() rather than once in createConfig(), so calling userEvent.setup() in a beforeEach before useFakeTimers() also works. An explicit advanceTimers option still takes precedence.

Verification

Two scratch projects, Vitest 4.1.10 and Jest 30.4.1, both on jsdom 30, run against packed builds of this branch and of #1304:

Run This branch #1304
Vitest, globals: false (default) 8/8 pass 3 fail, fake timer tests time out
Vitest, globals: true 8/8 pass 1 fail, real timer click throws
Jest 30, modern fake timers 6/6 pass 2 fail, time out

Covered: click and delayed type() under fake timers with no options, fake timers installed after setup(), toFake: ['Date'] only, explicit advanceTimers still honored, real timers clean, Jest legacy timers.

This repo's test env installs @sinonjs/fake-timers the same way Jest and Vitest do, so the unit tests exercise the real mechanism instead of hand-assigned globals.

Notes for review

  • setTimeout.clock is undocumented @sinonjs/fake-timers internals. @testing-library/dom already relies on it, so the risk is shared, but it is a dependency on an implementation detail.
  • Jest's legacy fake timers expose no clock, so those users still need advanceTimers, unchanged from today.
  • waitFor in @testing-library/dom sniffs the jest global separately, so Vitest users under fake timers may still need the globalThis.jest shim for findBy* queries. Out of scope here.
  • The docs never mention Vitest for advanceTimers. Happy to send a docs PR alongside this.

Both frameworks install `@sinonjs/fake-timers`, which exposes the clock
on each timer function it replaces. Ticking that clock advances fake
timers whichever framework installed them - and only while they are
installed, so real timers are untouched.

Fixes testing-library#1115
@TrevorBurnham
TrevorBurnham force-pushed the fix-fake-timers-clock-detection branch from 812e1c9 to 9e85866 Compare August 15, 2026 23:16
@TrevorBurnham TrevorBurnham changed the title feat: advance fake timers installed by Jest or Vitest feat: automatically advance fake timers in Jest and Vitest Aug 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

userEvent.click() fails when used with vi.useFakeTimers(), all available solutions are not working

1 participant