feat: automatically advance fake timers in Jest and Vitest - #1324
feat: automatically advance fake timers in Jest and Vitest#1324TrevorBurnham wants to merge 2 commits into
Conversation
812e1c9 to
9e85866
Compare
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
9e85866 to
c9d16de
Compare
|
Thanks, I spent some time looking over this today. The issue this PR is closing I think is actually addressed by testing-library/react-testing-library#1443 So if we're aiming for vitest and jest working the same to start, which I think is the best interpretation of that issue, then I think the first change is to update Once those two work the same, then I think we can consider this PR and a different/new Issue to add automatic detection of fake timers. At least right now, vitest just breaks until you have the right setup instead of sometimes working. One more thing to note, this changes the API default for advanceTimers, it goes from |
|
You're right: testing-library/react-testing-library#1443 fixes #1115 insofar as it makes this setup work with Vitest fake timers: const user = userEvent.setup({advanceTimers: vi.advanceTimersByTime})I think detecting and handling the most common fake timer implementations automatically is the better solution for users, but I also realize that it has a larger blast radius and might want to wait for a new major version (is there a roadmap for one?). |
This PR makes
userEventautomatically advance fake timers under both Jest and Vitest, without the need for users to configureadvanceTimersmanually.Fixes #1115
Approach
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:This works however the framework is imported, and only while fake timers are installed, so real timers are never touched. It's the same signal
@testing-library/domuses injestFakeTimersAreEnabled().Detection happens per
wait()rather than once increateConfig(), so callinguserEvent.setup()in abeforeEachbeforeuseFakeTimers()also works. An explicitadvanceTimersoption still takes precedence.This does not add support for auto-advancing Jest's legacy fake timers, which don't expose a clock. Modern fake timers have been the default since Jest 27 (2021): https://jestjs.io/blog/2021/05/25/jest-27
Relevant docs
The "Using Fake Timers" and "Options → advanceTimers" docs should be updated once this is merged. Currently they only mention Jest, not Vitest, and recommend this piece of setup:
Happy to send a docs PR alongside this.
Verification
I've confirmed that timers automatically advance under the latest Vitest and Jest with fake timers enabled.
Covered: click and delayed
type()under fake timers with no options, fake timers installed aftersetup(),toFake: ['Date']only, explicitadvanceTimersstill honored, real timers clean.This repo's test env installs
@sinonjs/fake-timersthe same way Jest and Vitest do, so the unit tests exercise the real mechanism instead of hand-assigned globals.Notes for review
setTimeout.clockis undocumented@sinonjs/fake-timersinternals.@testing-library/domalready relies on it, but it's a notable dependency on an implementation detail.waitForin@testing-library/domsniffs thejestglobal separately, so Vitest users under fake timers may still need theglobalThis.jestshim forfindBy*queries.Making that "just work" would be a natural follow-up.
jest.useFakeTimers({advanceTimers: true}),vi.useFakeTimers({shouldAdvanceTime: true})) now get both auto-advance and an explicittick(delay). At the defaultdelay: 0no additional virtual time is advanced, sincetick(0)fires the timers already due without moving the clock. With a non-zero delay, the clock moves by that delay on top of auto-advance. Sinon setsclock.attachedIntervalonly when auto-advance is on, so the tick could be skipped, but advancing by the delay the caller asked for seems more correct than deferring to real time.