What Changed in Vitest 5
See what changed in Vitest 5, including performance gains, breaking changes, new defaults, reporter paths, and a practical checklist for upgrading your tests and CI.
Vitest 5.0, released on September 3, 2026, is a performance-focused major that requires Node.js 22.12.0+ and Vite 6.4.0+, turns on mock clearing by default, fails unawaited async assertions, and moves reporter output under a single .vitest/ directory.
The failures after the upgrade are mostly easy. The hard ones are the tests that pass locally on Vitest 4 and go red in CI with an error that says nothing about why.
This article sorts the v5.0.0 release notes by impact. First, what actually got faster. Then the changes that need code edits, the changes that quietly move paths and matching rules, an upgrade checklist, and a verdict.
Key Takeaways
- Vitest 5.0 requires Node.js 22.12.0 or later and Vite 6.4.0 or later.
- The Vitest team’s benchmarks show most tested setups running 8 to 25% faster, and up to 53% faster in some VM-pool setups. Setups dominated by environment setup barely change.
clearMocksnow defaults totrue, so an assertion on call history recorded in a setup file, abeforeAllhook or an earlier test now sees zero calls.- Blob reports, attachments, and JSON, JUnit and HTML reporter output now default to paths under
.vitest/, so CI artifact steps need updating. toThrow('')now matches any thrown error, so an assertion meant to check for an empty message needs an explicit pattern.
Why Does Vitest 5 Run Faster?
Most setups in the Vitest team’s own benchmarks run 8 to 25% faster, according to the Vitest 5 announcement. VM pools gain the most, with up to 53% in some setups. Not every setup speeds up: runs where creating the test environment takes most of the time, such as forks with jsdom and isolation, stay within 3% of Vitest 4.1. The numbers come from vitest-dev/benchmarks, where the team generated test apps of different sizes, from a small 5-file package up to a 1,280-module monolith. In VoidZero’s launch post, this is rounded to “vm pools up to 53% faster, ~18% boost across the board including Browser Mode”.
According to the release notes, four changes produce most of the gain:
- Shared Vite server. Inline projects now share one Vite server instead of each starting its own.
fsModuleCache. This is now a top-level option. It saves transformed modules to disk, so a rerun or another Vitest process can skip that work.- Fewer round trips. Modules that are already transformed now reach a worker in one trip from the main process.
- VM pool reuse. The
vmThreadsandvmForkspools share compiled code between contexts and load the module graph ahead of time.
Vitest also supports Node’s on-disk compile cache, but it is opt-in.
Which Vitest 5 Changes Need a Code Edit?
Six Vitest 5 changes cause a test failure or a config error on the first run. The migration guide covers each one.
| Change | First-run symptom | Fix |
|---|---|---|
clearMocks: true by default | Call-count assertions see 0 | Trigger calls inside the asserting test, or set clearMocks: false |
| Unawaited async assertion | Test fails | Add await |
Hoisted vi call outside top level | Throws | Move it to module scope |
sequential removed | Removed API | { concurrent: false } |
| No parent-directory config lookup | Config not found | Add a config to the package folder |
| Bench API rewritten | Old bench code breaks | Move to the fixture model |
Mock Clearing and Hoisting
In Vitest 5, clearMocks defaults to true, so vi.clearAllMocks() runs before every test. Call history recorded in a setup file, a beforeAll hook or an earlier test is gone before the next test asserts on it. Mock implementations are left in place.
// Vitest 5.0.x
const track = vi.fn()
beforeAll(() => initAnalytics(track))
it('tracks once on init', () => {
expect(track).toHaveBeenCalledTimes(1) // now receives 0 calls
})
The fix is to trigger the call inside the test that asserts on it. Setting clearMocks: false in test config restores the old behaviour while you audit the suite.
Calling vi.mock, or any other hoisted vi call, anywhere except the top level of a file now throws. Vitest hoists these calls to the top of the module anyway, so code inside a describe block never ran where it was written.
// Vitest 5.0.x: throws
describe('UserCard', () => {
const fetchUser = vi.fn()
vi.mock('./api', () => ({ fetchUser }))
})
// Vitest 5.0.x: works
const { fetchUser } = vi.hoisted(() => ({ fetchUser: vi.fn() }))
vi.mock('./api', () => ({ fetchUser }))
describe('UserCard', () => {
it('renders the user', async () => {
fetchUser.mockResolvedValue({ name: 'Ada' })
// mount and assert
})
})
If your Vue suites mock their API layer, the same top-level pattern applies to mocking API calls in Vue tests with Vitest.
Unawaited Assertions
A test that leaves an async assertion unawaited now fails. A missing await before expect(...).resolves or .rejects turns the test red.
// Vitest 5.0.x
test('loads config', async () => {
expect(loadConfig()).resolves.toEqual({ ok: true }) // fails
await expect(loadConfig()).resolves.toEqual({ ok: true }) // passes
})
This grep lists candidates. You still need to check each hit for a leading await:
grep -rnE "expect\(.*\)\.(resolves|rejects)" src/ --include='*.test.*' --include='*.spec.*'
Concurrency, Config Lookup and Bench
The sequential options on tests and suites are gone. To opt out of concurrency, use test('example', { concurrent: false }, ...) or describe('suite', { concurrent: false }, ...).
Vitest 5 stops looking in folders above the current one for a config file. If you run vitest from a package subfolder, that folder needs its own config.
The bench API was rewritten. You no longer import bench at the top of a file. Instead, you take it from the test context inside an ordinary test() call in a benchmark file.
The release notes list other breaking items to check if they apply to you:
expect.pollnow fails when it times out.- Deprecated entry points were removed.
@vitest/runneris deprecated, andvitestno longer depends on@vitest/expectbecause the assertion code now ships insidevitestitself.- The
@vitest/browser-webdriverioprovider moved to the vitest-community organization and is now maintained by the community. workerIdis now 1-based.
toThrow('') now matches any thrown error. If you really want to check for an empty message, pass a regex such as /^$/ instead.
What Changes Silently in Vitest 5?
Six Vitest 5 changes don’t throw an error. Instead, a path, a filter or a match result changes underneath your existing setup.
- Output paths. Blob reports and
--merge-reportsdefault to.vitest/blob/. Attachments move from.vitest-attachements/to.vitest/attachments/. JSON, JUnit and HTML reporter files also default to.vitest. -tfilters. The separator for test-name filters is now>. Check any CI scripts that filter by suite path.- Browser locators.
locators.exactis now on by default in Browser Mode. - Text matching.
toHaveTextContentis now strict.toMatchTextContentis the new alternative. - Coverage globs.
includeandexcludepatterns now match each file’s path relative to the project root, and a pattern with no wildcard counts as a whole folder. The set of files counted toward coverage can change, so check your thresholds after the first run. - Inline projects. Inline projects now inherit the root config as if
extends: truewere set.
A typical artifact step changes like this:
- path: .vitest-attachements/
+ path: .vitest/attachments/
+ # sharded runs: upload .vitest/blob/ for --merge-reports
New in Vitest 5 and Worth Knowing
vi.when lets you give a spy a different result for each set of arguments. calledWith accepts asymmetric matchers, and calls whose arguments match nothing fall through to the original implementation.
// Vitest 5.0.x
vi.when(getUser).calledWith(1).thenResolve({ id: 1, name: 'Ada' })
In Browser Mode, setting test.browser.traceView: true turns on the trace view. Each interaction, assertion and page.mark call is saved as a DOM snapshot, so you can replay the test one step at a time in the UI.
Nested projects are now supported, which helps monorepos group related projects.
Vitest 5 Upgrade Checklist
- Move CI and local environments to Node.js 22.12.0+ and Vite 6.4.0+.
- Run the grep above and add
awaitwherever it’s missing. - Move every
vi.mockandvi.hoistedcall to the top level of its file. - Replace
sequentialwith{ concurrent: false }, and add configs to package folders that relied on a parent config. - Run the suite. If call-count assertions fail, fix them or set
clearMocks: falseas a temporary bridge. - Update CI artifact paths to
.vitest/, and review-tfilters and coverage thresholds.
Should You Upgrade to Vitest 5 Now or Wait?
Upgrade to Vitest 5 this sprint if your CI already runs Node.js 22.12.0+ and Vite 6.4.0+. Most of the required edits are mechanical.
The exception is a suite that asserts on mock call history carried across tests, whether from setup files, beforeAll hooks, or one test relying on another test’s calls. Those failures give no hint of the cause, so audit those assertions first and then upgrade. Component suites should also re-run their text and locator assertions; the patterns in testing Svelte 5 components with Vitest show where they tend to appear.
Vitest 5 is faster, and most of what it breaks is test code that was already wrong. Start with the grep and the vi.mock move on a branch, check the CI artifact paths, and let the first CI run point you to the rest.
FAQs
Does Vitest 5 also turn on mockReset or restoreMocks by default?
No. The Vitest 5 migration guide changes the default only for clearMocks. clearMocks calls vi.clearAllMocks() before each test and resets mock.calls, mock.instances, mock.contexts and mock.results, but keeps implementations. mockReset goes further. It clears history and resets each implementation to its original, so a mock created with vi.fn(impl) goes back to impl. restoreMocks puts back the original implementations of spies created with vi.spyOn.
Why does my -t filter match fewer tests after upgrading to Vitest 5?
In Vitest 5, testNamePattern (the -t flag) is checked against the full test name, built by putting ' > ' between each suite name and the test name. That is the same text you see in the reporter output. Vitest 4 used a single space between the parts, like Jest. A pattern only breaks if it crosses from one part of the name into the next. To fix it, match just one part, such as -t adds, or put a wildcard between the parts, such as -t 'math.*adds'.
Why can't Vitest 5 resolve vite after upgrading with Yarn?
In Vitest 5, vite moved from a direct dependency to a required peer dependency, so Vitest runs on whatever Vite version your project installs. npm, pnpm, Bun and Deno add peer dependencies for you. Yarn leaves that step to you. Add vite at version 6.4.0 or later to your package.json and reinstall, and Vitest can resolve it again.
How do I merge sharded test reports in Vitest 5?
Run each shard with the blob reporter, for example vitest run --reporter=blob --shard=1/3 on the first machine. Each shard writes its results to .vitest/blob/ by default, and the --outputFile.blob flag changes that location. Copy the directory from every machine into one final job and run vitest --merge-reports. If your tests save attachments as files, bring the attachments folder into the merge job as well.
Gain Debugging Superpowers
Unleash the power of session replay to reproduce bugs, track slowdowns and uncover frustrations in your app. Get complete visibility into your frontend with OpenReplay — the most advanced open-source session replay tool for developers.
Star on GitHub12k