Vitest 5.0.0 landed on September 3, 2026, and most of the coverage since has been about what breaks. Fair enough; we wrote that piece too, and if you haven't upgraded yet, start with the Vitest 5 migration guide, because nothing below matters while your install fails on Node 20. This piece is the other half: the new capabilities, ranked by payoff, once the suite is green again. The repo I work in moved its root vitest dependency from ^4.1.0 to ^5.0.0 on September 9, six days after the release, and I re-ran the examples below on 5.0.0 with Node 22.23.2 while writing this. A few of these features earned their place fast. Others I'd leave alone for now.
Is the Speed Really Free?
Performance headlines from tool vendors deserve suspicion. What makes these different is that the methodology is public: the team measured against vitest-dev/benchmarks, a suite of real project shapes ranging from a 5-file utility library to a 1,280-module monolith, across pools and environments. From the announcement post:
| Scenario | Vitest 4 | Vitest 5 | Change |
|---|---|---|---|
| deps-heavy project, vm pool | 1.59s | 0.74s | -53% |
| enterprise-monolith, isolated | 7.24s | 5.83s | -19% |
| React/Vue SPA, Browser Mode | baseline | faster | -16 to -18% |
The spread across all configurations is 8 to 25 percent. Where will your suite land? Somewhere in that band, depending on pool and isolation settings, and the new vitest doctor command will tell you exactly where: it reruns your suite under alternative configurations and prints the options that would make it faster. That's a genuinely new idea for a test runner. Configuration advice has always been folklore passed between blog posts; here it's measured against your own code.
Two config options do the durable work. fsModuleCache, promoted from experimental.fsModuleCache, persists transformed modules to disk so reruns skip transformation entirely. And sharedViteServer lets projects in test.projects that don't alter the Vite config reuse one server instead of each booting their own, which is exactly the shape most monorepo test setups have.
How Does vi.when Simplify Mock Code?
Every TypeScript codebase with service mocks has that one mockImplementation callback with a switch statement inside. You know the one. The new vi.when API replaces the pattern with per-argument stubbing:
import { vi, test, expect } from 'vitest'
const findById = vi.fn()
vi.when(findById).calledWith(1).thenResolve({ id: 1, name: 'Ella' })
vi.when(findById).calledWith(2).thenResolve({ id: 2, name: 'Marcus' })
vi.when(findById).calledWith(expect.any(String)).thenReject(new TypeError('ids are numbers'))
Asymmetric matchers work as arguments, so the third line catches a whole class of calls rather than one value. The companion assertion toHaveBeenExhausted() fails the test when a stubbed behavior was never consumed, which catches the quiet rot where a test keeps stubbing a call path the component stopped making months ago. One catch I hit running it: it takes the chain, not the mock. Keep the object that vi.when(spy).calledWith(...).thenReturnOnce(...) returns and assert on that, because expect(findById).toHaveBeenExhausted() throws a TypeError saying the mock is not a vi.when instance.
Is this jest-when absorbed into core? Essentially, yes. And that's the right call: the community proved the API shape since jest-when's first npm release in 2018, and first-party support means it tracks the mock internals instead of trailing them. I haven't rewired a large mock layer with it, so I can't promise you a diff of pure deletions; the stubs above ran clean on 5.0.2 when I re-ran them on Node 22.23.2, and that's the extent of what I've checked. If your components lean on typed query hooks, the pattern in our React data fetching guide pairs naturally with calledWith on query keys.
Silent failure is a theme this release keeps attacking, from unawaited assertions to unconsumed stubs. If you like that direction, aim the same suspicion at your linter: our free lint coverage audit script runs seven checks for files ESLint never opens and CI steps that can't fail, which is the same disease in a different organ.
Do Fake Timers Understand Temporal Now?
vi.useFakeTimers() and vi.setSystemTime() now mock the Temporal API alongside Date, courtesy of the @sinonjs/fake-timers 15.4 update. If part of your code reads Temporal.Now while older modules still call Date.now(), both now agree under fake timers, and toNotFake: ['Temporal'] opts back out where you need the real clock. New to the API itself? Our Temporal API explainer walks through the types these timers now fake.
vi.useFakeTimers({ now: new Date('2026-09-14T09:00:00Z') })
expect(Temporal.Now.instant().toString()).toBe('2026-09-14T09:00:00Z')
One limit on what I checked: Temporal isn't a global on the Node 22.23.2 I ran these on, so this example is the release notes' claim rather than mine. Why does that matter now? Because codebases are mid-migration. Nobody flips date handling in one commit, and until this release your tests could not fake time consistently across the seam. This closes it.
What Is the New Browser Mode Flight Recorder?
How many times have you rerun a flaky browser test purely to watch what the DOM does? Trace View is the feature I'd upgrade for. Enable browser.traceView and every interaction and assertion in a browser test records a DOM snapshot you can step through after a failure, the same investigation style Playwright users have had for years, now inside component tests. A flaky click sequence stops being a matter of rereading the test and imagining the DOM.
Locator failures got smarter too. When getByRole finds nothing, Vitest now prints the ARIA snapshot of the subtree it searched, so the error shows you the roles and names that actually exist instead of leaving you to console.log the DOM. The format is tunable through browser.locators.errorFormat. Combined with strict-by-default locators, browser tests fail with evidence now. That changes review culture more than any single API: a failing screenshot plus an ARIA tree is an argument, not a shrug. We lean on exactly these signals when testing AI-generated React components, where the test author didn't write the markup and needs the runner to describe it honestly.
Vitest 5's real theme is evidence: locator errors print the ARIA tree that actually exists, Trace View replays the DOM at every step, and toHaveBeenExhausted proves your stubs were consumed, so a failing test now argues its case instead of shrugging.
Why Are Benchmarks Replayable Now?
The old top-level bench import is gone. Benchmarking is now a test-context fixture inside regular test() calls in benchmark files, with two additions that change what benchmarks are for. bench.compare() runs variants against each other and stores results. bench.from() replays a stored result as a baseline, so CI can answer "did this PR make it slower than main" instead of printing numbers nobody compares.
test('serialize row', async ({ bench }) => {
const stored = await bench.from('./baselines/serialize.json')
await bench.compare(stored, () => serializeRow(fixture))
})
Honestly, most application teams shouldn't adopt this yet. A benchmark that runs on a shared CI runner measures the runner's mood as much as your code, and a baseline file invites false confidence. Library authors with hot paths are the audience here, and for them the platform question in our Node test runner comparison tilts a little further toward Vitest, because node:test offers nothing comparable.
In What Order Should You Adopt It?
Upgrade for the free speed and let vitest doctor argue about your pool settings. Move your worst mockImplementation switches to vi.when the next time a test in that file fails, not in a big-bang refactor. Turn on browser.traceView the same day if you run Browser Mode. Leave the benchmark API until you have a hot path with a named owner. The deeper story is that the runner now treats silent success as a bug in itself, and having run the examples against 5.0.0, I think that's the most durable change in the release. Speed estimates age; a test that can prove what it saw doesn't. The next release is already on the beta tag, and I timed its two selective-rerun flags in Vitest 5.1 Beta Tested.