Back to Blog
Full Stack

Upgrading to Vitest 5: Faster Runs, vi.when, and the Breaking Changes to Watch

Vitest 5.0 brings big speed gains, declarative mocking with vi.when, and stricter defaults. Here is what is new and how to upgrade a React or Next.js test suite safely.

Sanjay ThakurOctober 1, 20266 min read
Upgrading to Vitest 5: Faster Runs, vi.when, and the Breaking Changes to Watch

Vitest 5.0 was released on September 3, 2026, and it's one of the more substantial updates to the test runner so far. The headline is speed (the team reports up to 53% faster runs on dependency-heavy apps), but the release also tightens several defaults that will surface hidden problems in existing test suites. This post walks through the most useful new features and a practical upgrade path for a typical React or Next.js project.

Before You Upgrade: Minimum Versions

Vitest 5 requires:

  • Node.js 22.12.0 or newer
  • Vite 6.4.0 or newer

Check both first, since an older Node in CI is the most common reason an upgrade "works locally" and then fails:

node --version
npm ls vite
npm install -D vitest@5

If you use @vitest/ui, @vitest/coverage-v8 or @vitest/browser, upgrade them in the same step so all @vitest/* packages stay on matching versions.

New Features Worth Adopting

vi.when: Per-Argument Mock Behavior

Mocks that return different values depending on their arguments used to need a hand-written mockImplementation with if branches. The new vi.when API makes that declarative:

import { vi, test, expect } from 'vitest'
import { findById } from './users'

vi.mock('./users')

test('returns the user or rejects', async () => {
  vi.when(findById)
    .calledWith(1)
    .thenResolve({ id: 1, name: 'Ella' })
    .calledWith(expect.any(Number))
    .thenReject(new Error('not found'))

  await expect(findById(1)).resolves.toEqual({ id: 1, name: 'Ella' })
  await expect(findById(2)).rejects.toThrow('not found')
})

The more specific match is listed first, and asymmetric matchers like expect.any() work as fallbacks.

Projects Inherit the Root Config

In Vitest 5, projects inherit the root configuration by default. You no longer need extends: true on each one, and project config files can declare their own nested projects:

// vitest.config.ts
import { defineConfig } from 'vitest/config'

export default defineConfig({
  test: {
    globals: true,
    projects: [
      { test: { name: 'unit', environment: 'node', include: ['src/**/*.test.ts'] } },
      { test: { name: 'dom', environment: 'jsdom', include: ['src/**/*.test.tsx'] } },
    ],
  },
})

Run a single project with the new -p shorthand:

npx vitest run -p dom

Trace View for Browser Mode

Browser Mode can now record interactions and assertions as DOM snapshots and replay them step by step afterwards, in the browser UI, Vitest UI or the HTML reporter. Enable it with browser.traceView: true. It's a big help when a component test fails only in CI.

Hunting Flaky Tests

The new --repeats CLI option reruns tests to surface intermittent failures, and fake timers now mock the Temporal API alongside Date.

Breaking Changes That Will Bite

clearMocks Is Now true by Default

Mock call history is cleared before each test (implementations are kept). Tests that accidentally depended on calls recorded in a previous test will now fail, which is usually a real bug in test isolation. Fix the test rather than setting clearMocks: false globally.

Unawaited Async Assertions Fail

This was a silent source of false positives. A missing await now fails the test:

// Before: could pass silently even if the promise rejected
expect(fetchUser(1)).resolves.toMatchObject({ id: 1 })

// Vitest 5: the assertion must be awaited
await expect(fetchUser(1)).resolves.toMatchObject({ id: 1 })

The same applies to rejects and toMatchFileSnapshot. expect.poll also fails when its callback doesn't resolve in time.

Stricter Text and Locator Matching

toHaveTextContent is now strict; use the new toMatchTextContent for partial matches. In Browser Mode, locators.exact is on by default, so locators no longer match partial text by accident.

Removed and Moved Options

  • The sequential test and suite options are removed in favor of concurrent.
  • Config files are no longer looked up in ancestor directories, so monorepo packages need their own config or an explicit --config.
  • Reporter output (HTML, JSON, JUnit, blob) and attachments now go to a single .vitest directory by default.
  • The bench API was rewritten: benchmarks now use a bench fixture inside test().
  • The webdriverio package is removed from Browser Mode.

Add the new output directory to .gitignore:

echo ".vitest/" >> .gitignore

A Safe Upgrade Workflow

  1. Upgrade Node in CI to 22.12 or newer.
  2. Bump vitest and all @vitest/* packages together.
  3. Run the suite and fix unawaited assertions first; they point to real gaps.
  4. Fix isolation failures from clearMocks instead of disabling it.
  5. Update CI artifact paths to the .vitest directory.
  6. Read the official migration guide for anything specific to your setup.

Key Takeaways

  • Vitest 5.0 (September 3, 2026) needs Node.js 22.12+ and Vite 6.4+.
  • vi.when makes argument-specific mocking declarative.
  • Projects inherit root config by default; use -p to run one project.
  • clearMocks defaults to true, and unawaited async assertions now fail.
  • Reports and attachments move into a single .vitest directory.

Resources

VitestTestingTypeScriptTooling