Verdict
Six behaviours that passed on Vitest 4.1.11 failed or changed on 5.0.3, each with a clear message and a one-line fix. The sneaky one is test.each titles: snapshot keys change, and locally Vitest 5 quietly writes new snapshots instead of failing.
Tested on
- Vitest
- 4.1.11 → 5.0.3
- Vite
- 8.3.3
- Node
- 24.14.0
- OS
- Windows 11 Pro
Contents
- Before you upgrade01
- What broke, at a glance02
- 1. Mocks are cleared before every test03
- 2. Unawaited async assertions fail the test04
- 3. vi.mock inside a function fails the whole file05
- 4. Vitest no longer finds a config in a parent folder06
- 5. test.each titles changed, and so did snapshot keys07
- 6. expect.poll fails when the function is too slow08
- In the release notes, not covered by my tests09
- How this was tested10
Vitest 5.0 came out on September 3, 2026, and 5.0.3 is current. Its release notes list dozens of breaking changes, most of them in browser mode and the reporter API. To find the ones that break an ordinary test suite, I wrote small tests for the riskiest changes, confirmed every one passed on Vitest 4.1.11, then upgraded to 5.0.3 and ran the same files.
Before you upgrade
Vitest 5 needs Node ^22.12.0 || ^24.0.0 || >=26.0.0 and Vite 6.4 or newer (its engines and peerDependencies). On Node 20 or Vite 5, upgrade those first.
What broke, at a glance
| Change | On Vitest 4.1.11 | On Vitest 5.0.3 |
|---|---|---|
| Mocks cleared before each test | call counts carried over | expected "vi.fn()" to be called 2 times, but got 1 times |
| Unawaited async assertions | passed | Promise returned by expect(actual).resolves.toBe(expected) was not awaited |
vi.mock inside a function |
passed, with a warning | whole file fails: 1 call … was defined outside of the module's top level scope |
| Config in a parent folder | found | not found: ReferenceError: test is not defined |
test.each titles with $name |
greets 'alice' |
greets alice, so snapshot keys change |
expect.poll past its timeout |
waited 3 s and passed | expect.poll() function didn't resolve in time. |
And one that goes the other way: expect(fn).toThrow('') failed on 4.1.11 (expected [Function] to throw error matching /^$/ but got 'boom') and passes on 5.0.3, which reverted that behaviour.
1. Mocks are cleared before every test
A spy defined once at the top of a file used to keep counting across tests. Vitest 5 clears it before each one:
const spy = vi.fn();
test('first call', () => { spy('a'); expect(spy).toHaveBeenCalledTimes(1); });
test('counts calls across tests', () => { spy('b'); expect(spy).toHaveBeenCalledTimes(2); });
AssertionError: expected "vi.fn()" to be called 2 times, but got 1 times
The new default is usually what you wanted. A test that depends on calls from an earlier test is order-dependent. Fix the test to set up its own calls. To restore the old behaviour while you migrate:
export default defineConfig({ test: { clearMocks: false } });2. Unawaited async assertions fail the test
expect(promise).resolves… and .rejects… return a promise. Without await, Vitest 4 let the test finish before the assertion ran. Vitest 5 fails it:
Error: Promise returned by `expect(actual).resolves.toBe(expected)` was not awaited.
This assertion is asynchronous and must be awaited; otherwise, it is not guaranteed
to complete before the test finishes
Make the test async and await the assertion:
test('resolves', async () => { await expect(Promise.resolve(1)).resolves.toBe(1); });These failures are worth fixing rather than silencing: an unawaited assertion could never fail your test, even when the value was wrong.
3. vi.mock inside a function fails the whole file
Vitest 4 printed a warning and ran the file anyway:
Warning: A vi.mock('node:fs') call in "test/hoist.test.js" is not at the top level of the module.
… This will become an error in a future version.
On 5.0.3 it’s that error, and no test in the file runs:
Error: 1 call in "test/hoist2.test.js" was defined outside of the module's top level scope:
- vi.mock('node:path') at test/hoist2.test.js:2:20
Although it appears nested, it will be hoisted and executed before anything in this file.
Move it to the top level to reflect its actual execution order.
Move vi.mock (and vi.hoisted) to the top level of the file. It was always hoisted there anyway; the error just makes the code say so.
vi.mock('node:fs', () => ({ readFileSync: () => 'mocked' }));If you upgrade from a version that printed that warning, search your test output for is not at the top level first. Those are exactly the files Vitest 5 will refuse.
4. Vitest no longer finds a config in a parent folder
Running Vitest from a subfolder used to pick up the project’s vitest.config.js from above. Vitest 5 only looks in the folder you run it from. My root config set globals: true, so the test in sub/ lost test itself:
ReferenceError: test is not defined
It can show up as any missing setting: globals, aliases, setup files, environment. Monorepos where a package runs vitest without its own config are the usual victims.
Point it at the config, or run from the root:
npx vitest run --config ../vitest.config.js5. test.each titles changed, and so did snapshot keys
String values in a $name title are no longer quoted:
Vitest 4.1.11: ✓ greets 'alice'
Vitest 5.0.3: ✓ greets alice
That breaks -t "greets 'alice'" filters, and it renames every snapshot written from such a test. Here’s what happened to a snapshot file written by Vitest 4:
- On your machine, Vitest 5 writes a new snapshot under the new name, marks the old one obsolete, and passes. The new snapshot was never compared with anything.
- In CI (
CI=true), nothing is written, and the run fails:
Error: Obsolete snapshots found when no snapshot update is expected.
Error: Snapshot `greets alice 1` mismatched
After upgrading, update snapshots once, locally, and review the diff:
npx vitest run -uCheck the diff before committing: every renamed key should only be a renamed key, with the same content.
6. expect.poll fails when the function is too slow
expect.poll retries until its timeout. On Vitest 4, a callback that was still pending at the timeout was simply waited for. Mine resolved after 3 seconds against a 500 ms timeout, and the test passed in 3 s. On 5.0.3 it stops at the timeout:
expect.poll() function didn't resolve in time.
Raise timeout to what the call really needs, or make each call faster. The poll is meant to retry a quick check many times, not wait on one slow one.
In the release notes, not covered by my tests
These are listed as breaking in the 5.0.0 notes, but I didn’t reproduce them: json and junit reporters write to .vitest/ by default, -t uses > between suite and test names, the sequential option is removed in favour of concurrent (my { sequential: true } describe still ran without an error), browser locators changed, and the benchmark API was rewritten. If you use any of them, read the release notes for those items.
How this was tested
One project on Windows 11 with Node 24.14.0 and Vite 8.3.3. Each test file was written to pass on Vitest 4.1.11, and did (all except the toThrow('') case above). Then Vitest was upgraded to 5.0.3 on a clean node_modules and the same files were run unchanged. Each fix was applied and run again. The snapshot case used a snapshot file written by 4.1.11 and was run both normally and with CI=true. Error text is copied from the runs.
— N.K., end of entry No.043