Skip to content

What Actually Changes When You Upgrade to Vitest 5

Vitest 5.0.0 shipped on September 3, 2026. Here is every breaking change that will hit your test suite, with the exact fix for each one.

· · 6 min read
Source code on a dark screen with syntax highlighting

Quick Take

Vitest 5 clears mocks by default, throws on hoisted calls that used to only warn, drops test.sequential, and moves every artifact into .vitest/. Most suites need four small edits, not a rewrite.

Vitest 5.0.0 was published on September 3, 2026, eleven months after 4.0.0 landed in October 2025. It requires Node.js 22.12 or newer and Vite 6.4 or newer, clears mocks before every test by default, removes test.sequential, turns several old warnings into thrown errors, and moves every generated artifact into a single .vitest/ directory. I upgraded two suites the morning it dropped. One took nine minutes; the other took an hour, entirely because of mock call counts that had been quietly wrong for months.

Quick take: Vitest 5 needs Node 22.12+ and Vite 6.4+. Four changes cause almost all the breakage: clearMocks defaults to true, vi.mock/vi.hoisted throw when they are not at the top level, test.sequential and describe.sequential are gone in favor of { concurrent: false }, and unawaited resolves/rejects assertions now fail instead of passing silently. Output paths moved under .vitest/, which breaks CI artifact uploads before it breaks any test.

Which Versions Does Vitest 5 Need?

Start here, because nothing else matters if the install fails.

RequirementVitest 4Vitest 5
Node.js20.19+22.12+, 24, or 26+
Vite5.0+6.4, 7, or 8
@types/nodeany^22 or >=24

Node 20 hit end of maintenance in April 2026, so this is less aggressive than it looks. Still on v20? That upgrade comes first, and the Node 20 to 24 migration path has its own set of removals. The Vite floor matters for anyone running multi-target builds through the Vite 6 Environment API, since Vitest 5 resolves projects through that machinery.

A desktop computer on a wooden desk in a home office
Photo by Ryland Dean on Unsplash

Half of these version floors trace back to your compiler settings rather than to Vitest. If moduleResolution and lib in your tsconfig.json still describe a Node 18 world, our tsconfig generator produces a baseline that matches the runtime you actually target, which removes a whole category of confusing resolution errors before you start.

Laptop, notebook and mug on a desk cleared for a migration morning
Photo by Daniil Komov on Unsplash

Mocks Clear Themselves Now

This is the change that will cost you the most time. clearMocks used to default to false; it now defaults to true, and Vitest calls vi.clearAllMocks() before each test.

// Vitest 4: call history survived across tests
const fn = vi.fn()

test('first', () => {
  fn()
})

test('second', () => {
  fn()
  expect(fn).toHaveBeenCalledTimes(2) // passed, counting both tests
})
// Vitest 5: history is cleared before each test
test('second', () => {
  fn()
  expect(fn).toHaveBeenCalledTimes(1) // only this test's call
})

Implementations set with mockImplementation survive. Only the recorded calls go.

Restoring the Old Behavior

One config line brings v4 semantics back:

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

export default defineConfig({
  test: { clearMocks: false },
})

Should you? I'd say no, and I'll defend it: a test that depends on a call made by a different test is not a test, it's a coincidence with a green checkmark. Use the escape hatch to unblock a release, then fix the assertions properly.

Hoisted Calls Must Be Top Level

vi.mock(), vi.unmock() and vi.hoisted() are hoisted to the top of the file at transform time. Calling them inside a describe block never worked the way people expected. Vitest 4 warned. Vitest 5 throws.

// Throws in Vitest 5
describe('calculator', () => {
  vi.mock('./calculator')
})

// Correct
vi.mock('./calculator')

describe('calculator', () => {
  // ...
})

Grep for vi.mock with leading whitespace and you'll find every offender in about ten seconds.

sequential Is Gone

test.sequential and describe.sequential were removed. The options object replaces them.

// Removed in Vitest 5
test.sequential('writes the file', async () => {})
describe.sequential('db suite', () => {})

// Replacement
test('writes the file', { concurrent: false }, async () => {})
describe('db suite', { concurrent: false }, () => {})

Same behavior, one spelling instead of two. Hard to argue with that.

Unawaited Assertions Fail

An assertion chain through resolves, rejects or toMatchFileSnapshot returns a promise. Forget the await and Vitest 4 let the test pass regardless of the outcome. Now it fails the test.

// Passes in v4 even when the promise rejects, fails in v5
expect(loadUser(1)).resolves.toEqual({ id: 1 })

// Correct
await expect(loadUser(1)).resolves.toEqual({ id: 1 })

expect.poll changed too: it rejects when the callback doesn't settle in time, and the callback receives an AbortSignal you can hand to fetch.

await expect.poll(async ({ signal }) => {
  const res = await fetch('/api/status', { signal })
  return res.status
}, { timeout: 1000 }).toBe(200)

How many of your integration tests have been green for reasons nobody verified? Mine had two. Both were real bugs.

Output Moved Into .vitest

Every generated artifact now lives under one directory:

ArtifactVitest 4Vitest 5
Attachments.vitest-attachements/.vitest/attachments/
Blob reports.vitest-reports/.vitest/blob/
HTML reporthtml/.vitest/
JSON reporterstdout.vitest/json/output.json
JUnit reporterstdout.vitest/junit/output.xml

Note the old attachments path was misspelled. That typo is finally gone. Update .gitignore and any CI upload step that references the old paths, because an upload glob matching nothing does not fail a build, it just quietly produces an empty artifact. If your pipeline compares Vitest against the platform runner, the same caveat applies to the paths described in our Node test runner comparison.

Smaller Removals Worth Grepping For

  • Deprecated entry points are gone: vitest/coverage and vitest/reporters moved to vitest/node, vitest/environments and vitest/snapshot to vitest/runtime, and vitest/mocker was removed outright.
  • @vitest/runner was inlined and is no longer published separately.
  • toHaveTextContent is now a strict equality check; use the new toMatchTextContent for partial or regex matches.
  • The WebdriverIO browser provider moved out to the vitest-community organization.
  • VITEST_POOL_ID and VITEST_WORKER_ID are 1-based instead of 0-based, which matters if you shard databases by worker index.
  • vitest --ui requires the token printed in the terminal.

What You Get Back

The benchmark API was rewritten so bench is a test-context fixture with access to fixtures, hooks and assertions, rather than a top-level import. Fake timers now mock the Temporal API alongside Date, with toNotFake: ['Temporal'] as the opt-out. Class mocks keep prototype methods, so new MockedDog() instanceof Dog is finally true. And vi.when adds per-argument stubbing:

vi.when(findById).calledWith(1).thenResolve({ id: 1, name: 'Ella' })

That one deletes a lot of hand-rolled mockImplementation switch statements.

The Order That Worked for Me

Bump Node first, then Vite, then Vitest, running the suite between each step so failures have one obvious cause. Next, set clearMocks: false temporarily and confirm the suite is green, which separates version problems from mock problems. Then flip it back to true and fix what breaks. Finally, update CI paths and .gitignore for .vitest/. Read the full release notes before starting, especially if you use Browser Mode, where locators became strict and render() turned async.

Nine minutes or an hour. Which one you get depends entirely on how honest your mocks were before.

Frequently Asked Questions

Does Vitest 5 require a new Node.js version?
Yes. The published package declares engines of ^22.12.0 || ^24.0.0 || >=26.0.0, so Node 20 is out and even Node 22.11 is too old. It also needs Vite 6.4 or newer, with Vite 7 and 8 accepted as peers. Check both before you touch a single test file, because a version mismatch produces install errors rather than useful test failures.
Why do my tests suddenly see fewer mock calls?
Because clearMocks now defaults to true, so Vitest runs vi.clearAllMocks() before each test. Call history no longer leaks between tests. If a test asserted toHaveBeenCalledTimes(2) while counting a call made in an earlier test, it now sees 1. Set clearMocks: false in your config to restore the old behavior, though the leaking counts were usually hiding a bug.
Where did my coverage and JUnit output go?
Into the .vitest directory. Attachments moved from .vitest-attachements/ to .vitest/attachments/, blob reports to .vitest/blob/, the HTML report to .vitest/, JSON reporter output to .vitest/json/output.json, and JUnit XML to .vitest/junit/output.xml. CI jobs that upload a hardcoded path will silently upload nothing, so update those globs during the upgrade rather than after.