vitest-dev / vitest-dev/vitest
API for timer mocks that are safely isolated between tests
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 17.1k
- Forks
- 2k
- Avg merge
- 1d 22h
- Merged PRs (30d)
- 94
Description
Clear and concise description of the problem
Hello and thanks for the great project! 💙
Small disclaimer
I have seen comments in the #5665 and i'm aware that Vitest
cannot use AsyncLocalStorage in core since it's a Node.js API
But i think that this API idea might be useful, once AsyncContext proposal will be implemented in the browsers sometime in the future, so i decided to propose it anyway 😅
The problem
Current time mocks API has a noticeable disadvantage:
The vi.useFakeTimers()/useRealTimers() switch is global, at least for all tests in the current worker thread/child process
This, for example, may lead to problems, especially when running tests concurrently - since fake timers are not isolated between tests, they may leak between those and cause unexpected and confusing behaviour
As a side note: i probably would prefer, if vitest had banned using global useFakeTimers() switch along with test.concurrent in the same test suite 🤔
And even if we rule out .concurrent tests as a special case, the global switch behaviour is still may be quite confusing at times, since there are cases like
- libraries saving separate reference to e.g. original
setTimeoutbefore any mocking code was run, so user ends up with two concurrent time flows in the same test 🤯 - if
useRealTimersswitch was not called at the end of the test, next one also gets fake time anyway, which also may lead to unexpected and confusing behaviour, depending on theuseFakeTimersconfig
Suggested solution
So, since the core of the problem is the "global switch" nature of current fake timers API, the alternative must be "local" for the each test
I suggest API like this:
import { test, runWithFakeTime } from 'vitest'
test.concurrent('my-test-1', async () => {
await runWithFakeTime((timeControls) => {
const promise = startSomeTimeRelatedOperations()
timeControls.advanceTimeBy(42)
await p;
expect(...).toBe(...)
})
})
test.concurrent('my-test-2', async () => {
// does not get affected by time mocks in previous test
// even if was run concurrently
})
☝️ In this example it is expected, that time would be mocked only inside runWithFakeTime callback and any of event-loop tasks which were produced by it, which obviously requires API like AsyncContext
To do that all of the time-related APIs must be mocked in a way, that they use fake implementation if called inside runWithFakeTime context and native one if called outside of it
And i think, this mock should happpen before any other userland code in the suite was initialized, so any code, that saves separate reference to e.g. setTimeout, still gets mocked as expected
Unlike global switch, this API would allow for clear isolation of time mocks from any other code in the same suite, since "fake time zone" is separated into its own async context
Here is a little Proof-of-Concept example, which uses Node.js AsyncLocalStorage for this purpose:
https://stackblitz.com/edit/vitejs-vite-4hqcwk?file=tests%2Flib.js
Alternative
No response
Additional context
I have seen comments in the #5665 and i'm aware that Vitest
cannot use AsyncLocalStorage in core since it's a Node.js API
But i think that this API idea might be useful, once AsyncContext proposal will be implemented in the browsers sometime in the future
So i guess, this idea can wait until then
Validations
- Follow our Code of Conduct
- Read the Contributing Guidelines.
- Read the docs.
- Check that there isn't already an issue that request the same feature to avoid creating a duplicate.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by reading the proposed runWithFakeTime API, the AsyncContext proposal, and the linked #5665 discussion; the StackBlitz proof of concept is the only implementation reference provided. The work would be complete when fake timers are isolated per async test context, including spawned event-loop tasks, without affecting concurrent tests or native timers outside the context.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- node.js, typescript
- Domain
- testing, tooling
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 25/100