testTimeout behavior change with fake timers and concurrent mode in Vitest 3.x
0 reputation · 29 Jul 2022, 22:16 UTC
0 reputation · 29 Jul 2022, 22:16 UTC
Vitest test timeout is configurable globally via config testTimeout and per test via test(name, fn, timeout), with hookTimeout governing beforeEach/afterEach. The runner is documented to stop scheduling new tests on process signals such as SIGINT, and timeouts are described as real wall-clock.
The compatibility boundary between Vitest 2.x and 3.x, concurrent test execution, and vi.useFakeTimers creates uncertainty about cancellation semantics. Fake timers advance virtual time independently, while testTimeout is real time. Concurrent mode applies timeouts per test, but suite cancellation does not appear to propagate an abort signal into running test code.
With version assumptions for Vitest 2.x/3.x, clarification is needed on the interaction of these documented features.
Does testTimeout continue to measure wall-clock time when fake timers are active? Is there a documented mechanism for cooperative cancellation of in-flight async tests on timeout or SIGINT? How does concurrent mode change timeout enforcement compared to sequential mode across the 2.x to 3.x configuration transition?
32275 reputation · 30 Jul 2022, 00:00 UTC
In Vitest 3.x the testTimeout is measured against the real wall‑clock, even when a test has enabled fake timers. If a test never advances its fake timers, the worker will abort the test after the real‑time timeout has elapsed. There is no built‑in cooperative cancellation signal that propagates into the test body on timeout or SIGINT; each test simply fails when its timeout is hit.
testTimeout was effectively paused until fake timers were advanced. A test that never called vi.advanceTimersByTime() could run forever without triggering a timeout.Vitest runs each test file in its own worker. Each worker has an isolated fake‑timer context, so:
testTimeout. The abort is local to the test; it does not cancel other tests in the same or different workers.SIGINT or on timeout. The test simply receives a failure event.timeout.test.ts:import { test } from 'vitest';
// 500 ms real‑time timeout
const timeout = 500;
// Enable fake timers for this test only
vi.useFakeTimers();
test('fails after real‑time timeout', async () => {
// Do not advance timers – the test will hang until the real timeout
await new Promise(r => setTimeout(r, 1000));
}, timeout);
vitest run. The test should fail after ~500 ms real time, even though the fake timer was never advanced.--runInBand to see that the behavior is identical – the timeout is still enforced on the real clock.vi.advanceTimersByTime(1000); // or vi.runAllTimers();setup.ts), every test will be subject to this real‑time timeout rule. Tests that rely on real timers may still hit the timeout, which can be confusing.AbortController or similar to detect a timeout from within the test. Instead, design your async code to be idempotent or to handle a thrown TimeoutError if you need to react programmatically.To give a more precise recommendation, could you confirm whether you enable fake timers globally (in a setup file) or only within individual tests? Global fake timers can mask timeouts for unrelated tests, especially in concurrent mode.
Use comments to ask for clarification. Post a solution as an answer.
4,550 reputation · 30 Jul 2022, 02:09 UTC
Vitest implements testTimeout (and hookTimeout) with a native setTimeout that runs in the worker’s main thread. This timer is independent of the virtual timer context created by vi.useFakeTimers(). Consequently, advancing or running fake timers does not pause or reset the real‑world timeout; the worker will still terminate the test when the native timer expires, even if the test’s virtual clock is stalled.