
Vitest 5 shipped on September 3rd. If your project runs on Vite — React, Vue, Nuxt, SvelteKit, Solid, Astro — this is a mandatory read before you upgrade. Three breaking changes will silently fail your CI if you miss them. The payoff: vm pools are up to 53% faster, and you get a built-in Browser Mode Trace View. Here is what breaks, what you gain, and what to fix today.
Check Your Environment First
Vitest 5 raises the minimum bar for both runtimes. Before touching your package.json, confirm you meet the new requirements. Node 22 is the current LTS and Node 20 hit end-of-life in April 2026, so this upgrade is overdue for most teams.
- Node.js >= 22.12.0 — Node 20 is no longer supported
- Vite >= 6.4.0 — older Vite versions are incompatible
node --version # must be >= 22.12.0
npm list vite # must be >= 6.4.0
The Three Breaking Changes That Will Bite You
1. clearMocks Now Defaults to true
This is the change most projects will hit first. Vitest now calls vi.clearAllMocks() before every test automatically. Mock call history no longer carries over between tests. If your tests assert on mock calls from setup files, beforeAll hooks, or across multiple tests, those assertions will now see zero calls and fail — without an obvious error message pointing here.
// Before: history carried over — this passed
beforeAll(() => {
mockFetch.mockResolvedValue({ data: 'ok' })
})
it('calls fetch', () => {
render(<MyComponent />)
expect(mockFetch).toHaveBeenCalledTimes(1) // ❌ now sees 0 — cleared before test
})
// After: assert inside the test
it('calls fetch', () => {
render(<MyComponent />)
expect(mockFetch).toHaveBeenCalledTimes(1) // ✅
})
The fastest path to an unbroken upgrade: add clearMocks: false to your vitest.config.ts. That restores the previous behavior immediately. Longer term, restructure so mock assertions live inside the test that triggers the call — the pattern Vitest is nudging you toward, and the correct one.
2. Forgotten await on Async Assertions Now Fails the Test
Previously, expect(promise).resolves.toBe('ok') without an await would log a warning and the test would still pass — which is the problem. Tests omitting await on async assertions pass regardless of the actual result. They are lying to you. Vitest 5 turns that warning into a hard failure. This is the right call, but it means tests that were silently wrong will now show up as red.
// Before: warned, but test passed anyway
expect(fetchUser(1)).resolves.toEqual({ id: 1, name: 'Alice' })
// After: must await
await expect(fetchUser(1)).resolves.toEqual({ id: 1, name: 'Alice' })
Run this to find every instance in your codebase before upgrading:
grep -rn "\.resolves\.\|\.rejects\." --include="*.test.*" .
Add await to every hit. Tedious, but every one of those tests was giving you a false green.
3. vi.mock Called Inside describe or test Now Throws
Vitest has always hoisted vi.mock(), vi.unmock(), and vi.hoisted() to the top of the file at compile time. The warning existed because local variables referenced inside the mock factory might not be available. Vitest 5 upgrades that warning to a thrown error. Move all vi.mock() calls to the top level of the file — where they run anyway.
// Before: warned, worked most of the time
describe('UserService', () => {
vi.mock('./api') // ❌ throws in v5
})
// After: top level
vi.mock('./api') // ✅
describe('UserService', () => {
// test code
})
For mock values that need to change per test, use vi.hoisted() to create the reference before the factory runs:
const mockGet = vi.hoisted(() => vi.fn())
vi.mock('./api', () => ({ get: mockGet }))
it('handles network error', () => {
mockGet.mockRejectedValueOnce(new Error('network'))
// ...
})
What You Get in Return
53% Faster vm Pools, 18% Overall Speedup
The vmThreads and vmForks pools now reuse compiled code across contexts and pre-warm the module graph. Combined with the Node compile cache enabled by default, the result is up to 53% faster execution for projects using vm isolation, and approximately 18% faster across the board. No config changes needed — you get this on upgrade.
For large monorepos running hundreds of test files, this is the kind of gain that turns a 3-minute CI run into a sub-2-minute one. According to VoidZero’s announcement, the improvement applies across all pool types including Browser Mode.
Built-in Browser Mode Trace View
Enable browser.traceView: true in your Vitest config and Vitest records every interaction, assertion, and page.mark call as a DOM snapshot during Browser Mode test runs. Open the Vitest UI and replay tests step by step — the same experience as Playwright Trace Viewer, but integrated directly. Traces land in the new .vitest/ output directory. This closes the last meaningful gap between Vitest Browser Mode and Playwright for debugging purposes.
vi.when: Per-Argument Mock Behavior
The new vi.when() API defines what a spy returns based on which argument it receives, without writing a manual mockImplementation switch:
vi.spyOn(userService, 'getUser')
.when(1).thenReturn({ id: 1, name: 'Alice' })
.when(2).thenReturn({ id: 2, name: 'Bob' })
.thenReturn(null) // fallback for any other argument
Arguments match using deep equality, and asymmetric matchers like expect.any(Number) work. Small API addition, but it eliminates a lot of boilerplate in tests that mock data-fetching services with multiple return states.
Migration Checklist
- Confirm Node.js >= 22.12.0 and Vite >= 6.4.0 in your environment and CI
- Run the
grepcommand above to find unawaited async assertions — fix each one - Move all
vi.mock(),vi.unmock(), andvi.hoisted()calls to the top level of each test file - Add
clearMocks: falsetovitest.config.tsas a bridge; remove it after auditing cross-test mock dependencies - Update CI artifact paths from previous output locations to
.vitest/ - Run
vitest -uto regenerate any snapshots affected by the test title format change - Upgrade coverage plugin alongside:
npm install -D vitest@5 @vitest/coverage-v8@5
Check ecosystem packages — particularly @nuxt/test-utils and any framework-specific Vitest adapters — for v5 compatibility before upgrading in production. Some are still catching up. The full official migration guide covers every change; the Vitest 5 release blog has the complete feature list. The breaking changes here are the ones that will hit the widest range of codebases — fix these three and the upgrade will be mostly smooth.













