---
title: "Vitest 5: what changed for Vue tests · vuejs.school"
description: "Vitest 5.0 is faster, clears mocks before every test and fails unawaited assertions. What it changes for Vue tests, Browser Mode and Nuxt, and how to upgrade."
canonical_url: "https://vuejs.school/community/blog/vitest-5-what-changed-for-vue-tests"
meta:
  "og:description": "Vitest 5.0 is faster, clears mocks before every test and fails unawaited assertions. What it changes for Vue tests, Browser Mode and Nuxt, and how to upgrade."
  "og:image": "https://vuejs.school/_og/d/a_~Vml0ZXN0IDU6IHdoYXQgY2hhbmdlZCBmb3IgVnVlIHRlc3RzIMK3IHZ1ZWpzLnNjaG9vbA%2Cc_OgCard.takumi%2Ctitle_~Vml0ZXN0IDU6IHdoYXQgY2hhbmdlZCBmb3IgVnVlIHRlc3Rz%2Cdescription_~Vml0ZXN0IDUuMCBpcyBmYXN0ZXIsIGNsZWFycyBtb2NrcyBiZWZvcmUgZXZlcnkgdGVzdCBhbmQgZmFpbHMgdW5hd2FpdGVkIGFzc2VydGlvbnMuIFdoYXQgaXQgY2hhbmdlcyBmb3IgVnVlIHRlc3RzLCBCcm93c2VyIE1vZGUgYW5kIE51eHQsIGFuZCBob3cgdG8gdXBncmFkZS4%2Ceyebrow_~UmVsZWFzZSBub3RlcyDCtyA2IG1pbiByZWFk%2Ctint_mint%2Cp_Ii9jb21tdW5pdHkvYmxvZy92aXRlc3QtNS13aGF0LWNoYW5nZWQtZm9yLXZ1ZS10ZXN0cyI%2Cs_r6QHe5pZ9JLiVoIi.png"
  "og:image:alt": "Vitest 5: what changed for Vue tests · vuejs.school"
  "og:image:height": "600"
  "og:image:type": image/png
  "og:image:width": "1200"
  "og:title": "Vitest 5: what changed for Vue tests · vuejs.school"
  "twitter:image": "https://vuejs.school/_og/d/a_~Vml0ZXN0IDU6IHdoYXQgY2hhbmdlZCBmb3IgVnVlIHRlc3RzIMK3IHZ1ZWpzLnNjaG9vbA%2Cc_OgCard.takumi%2Ctitle_~Vml0ZXN0IDU6IHdoYXQgY2hhbmdlZCBmb3IgVnVlIHRlc3Rz%2Cdescription_~Vml0ZXN0IDUuMCBpcyBmYXN0ZXIsIGNsZWFycyBtb2NrcyBiZWZvcmUgZXZlcnkgdGVzdCBhbmQgZmFpbHMgdW5hd2FpdGVkIGFzc2VydGlvbnMuIFdoYXQgaXQgY2hhbmdlcyBmb3IgVnVlIHRlc3RzLCBCcm93c2VyIE1vZGUgYW5kIE51eHQsIGFuZCBob3cgdG8gdXBncmFkZS4%2Ceyebrow_~UmVsZWFzZSBub3RlcyDCtyA2IG1pbiByZWFk%2Ctint_mint%2Cp_Ii9jb21tdW5pdHkvYmxvZy92aXRlc3QtNS13aGF0LWNoYW5nZWQtZm9yLXZ1ZS10ZXN0cyI%2Cs_r6QHe5pZ9JLiVoIi.png"
  "twitter:image:alt": "Vitest 5: what changed for Vue tests · vuejs.school"
---

> Index of every page on this site: [llms.txt](https://vuejs.school/llms.txt)

## Faster runs, Trace View and `vi.when`

The team built a benchmark set of generated apps and compared Vitest 4.1.10 with 5.0. A few of the published results:

- A Vue single-page app with 37 components in Browser Mode on Chromium: 1.94 s down to 1.58 s, 18% faster.
- An enterprise-sized app with 1,280 modules, forks pool, isolated: 7.24 s down to 5.83 s.
- A dependency-heavy app on the `vmThreads` pool: 1.59 s down to 0.74 s.

Not every suite gains: runs dominated by environment setup, such as forks with jsdom and isolation, stay within 3% of 4.1.

To see where your own suite spends its time, read the reporter's duration line, which now shows percentages (environment, import, transform, tests). The new `vitest doctor` command goes further: it reruns your suite with other settings and suggests faster options.

Two additions help when you write and debug tests:

- **Trace View** for Browser Mode. With `browser.traceView: true`, Vitest records every interaction and assertion as a DOM snapshot that you can step through after a run, in the browser UI, Vitest UI or the HTML reporter.
- **`vi.when`** sets return values per argument on a spy: `vi.when(fetchUser).calledWith(1).thenResolve({ id: 1, name: 'Ada' })`.

## Mocks are cleared before every test

`clearMocks` now defaults to `true`. Before each test, Vitest calls `vi.clearAllMocks()`, which empties the call history of every mock but keeps its implementation. Tests can no longer depend on calls made by an earlier test, which removes a common cause of tests that pass or fail depending on their order.

The guide warns which tests are hit: those that record calls outside the test body, in a setup file, at the top of a module or in `beforeAll`. A Vue suite that mounts once and asserts many times is exactly that. Here `SignupForm` calls its `track` prop with `'signup_viewed'` when it mounts:

*src/components/\_\_tests\_\_/SignupForm.spec.ts**Breaks***

```
import { mount } from '@vue/test-utils'
import { beforeAll, expect, it, vi } from 'vitest'
import SignupForm from '../SignupForm.vue'

const track = vi.fn()

beforeAll(() => {
  mount(SignupForm, { props: { track } })
})

it('reports that the form was shown', () => {
  expect(track).toHaveBeenCalledWith('signup_viewed')
})
```

On Vitest 4 this passes. On Vitest 5 the call made during `mount` is cleared before the test runs, and the assertion fails with "Number of calls: 0". Mount where you assert:

*src/components/\_\_tests\_\_/SignupForm.spec.ts**Works***

```
it('reports that the form was shown', () => {
  const track = vi.fn()
  mount(SignupForm, { props: { track } })
  expect(track).toHaveBeenCalledWith('signup_viewed')
})
```

The fixed test also passes on Vitest 4, and it reads better: everything it checks is set up in front of you. If you had an `afterEach(() => vi.clearAllMocks())`, you can delete it.

To get the old behaviour back while you work through a large suite, set `clearMocks: false` in `vitest.config.ts`.

## Unawaited assertions and nested `vi.mock` now fail

Two mistakes that Vitest 4 only warned about are now errors.

**Unawaited async assertions.** `resolves`, `rejects` and `toMatchFileSnapshot` return promises. Vitest 4 awaited a forgotten one at the end of the test and printed a warning, so the assertion ran at a different moment from where you wrote it. Vitest 5 fails the test and points at the line:

*src/utils/\_\_tests\_\_/loadCart.spec.ts*

```
import { expect, it } from 'vitest'
import { loadCart } from '../loadCart'

it('loads the saved cart', async () => {
  expect(loadCart()).resolves.toEqual(['Milk']) // fails on Vitest 5
  await expect(loadCart()).resolves.toEqual(['Milk']) // the fix
})
```

The first line fails the test on Vitest 5 even though the promise resolves to the right value. Search your suite for `.resolves` and `.rejects` without an `await` in front.

**`vi.mock` inside a function.** `vi.mock`, `vi.unmock` and `vi.hoisted` are moved to the top of the file before anything runs, so a call written inside `describe` never ran where it appeared. Vitest 5 fails the whole file and lists each call with its location. Move it to the top level. `vi.doMock`, which is not hoisted, can still go anywhere.

## Browser Mode: async render and exact locators

Browser Mode runs component tests in a real browser through a provider package. With Vitest 5 you need `@vitest/browser-playwright` 5, which must match the Vitest version exactly, and vitest-browser-vue 3, published on 4 September. Three changes hit Vue tests:

1. `render` from vitest-browser-vue now returns a promise and must be awaited.
2. Locators match text exactly by default. `getByText('Buy')` no longer finds "Buy milk".
3. `toHaveTextContent` checks for exact equality and no longer accepts a regular expression. Partial and regex matches moved to the new `toMatchTextContent`.

This test for a `TodoList` component, which renders its `items` as a list, hits all three on Vitest 5. The second block is the fixed version:

*src/components/\_\_tests\_\_/TodoList.browser.spec.ts**Breaks***

```
import { expect, test } from 'vitest'
import { render } from 'vitest-browser-vue'
import TodoList from '../TodoList.vue'

test('shows the items', async () => {
  const screen = render(TodoList, { props: { items: ['Buy milk', 'Walk the dog'] } })
  await expect.element(screen.getByText('Buy')).toBeVisible()
  await expect.element(screen.getByRole('list')).toHaveTextContent('milk')
})
```

*src/components/\_\_tests\_\_/TodoList.browser.spec.ts**Works***

```
import { expect, test } from 'vitest'
import { render } from 'vitest-browser-vue'
import TodoList from '../TodoList.vue'

test('shows the items', async () => {
  const screen = await render(TodoList, { props: { items: ['Buy milk', 'Walk the dog'] } })
  await expect.element(screen.getByText('Buy milk')).toBeVisible()
  await expect.element(screen.getByRole('list')).toMatchTextContent('milk')
})
```

To ease the move, `browser.locators.exact: false` restores partial matching. Exact matching is the better default, though: a test that finds "Buy milk" when you asked for "Buy" could just as easily find the wrong row.

When a locator finds nothing, Vitest 5 also prints the accessibility tree of the searched area. It is usually shorter than the HTML and shows the roles and names `getByRole` matches against.

## Other breaking changes to check

- `-t` patterns match the full name joined with `' > '`, as the reporter shows it. `vitest -t 'cart adds'` becomes `vitest -t 'cart > adds'`. An old pattern that spans a `describe` and a test name now matches nothing: its tests are skipped and the run still passes.
- Inline projects in `test.projects` inherit the root config, plugins and aliases included, without `extends: true`. That helps when your Vue plugin is declared once at the root. Arrays are merged, so a project's `setupFiles` are added to the root's; set `extends: false` on a project to keep the old behaviour.
- Reports and artifacts go to one `.vitest` folder: the HTML report, failure screenshots and attachments. The `json` and `junit` reporters now write files there instead of printing to the terminal, so a CI script that piped their output should read the file instead.
- `toThrow('')` matches any error message, because an empty string is a substring of every string (Vitest 4 matched only an empty message). `expect.poll` fails when it times out.
- Vitest UI needs the token URL it prints when it starts.
- Config files are no longer looked up in parent folders.
- `VITEST_POOL_ID` and `VITEST_WORKER_ID` start at 1, not 0.
- Fake timers and `vi.setSystemTime()` also fake `Temporal` now.
- `bench` is no longer a top-level import: in benchmark files it is a fixture you receive inside `test()`.

## Upgrading a Vue project

Check the two requirements and start from a green suite. Then move every Vitest package together, because Vitest 5 expects its coverage, UI and browser packages at exactly its own version:

```
node --version
npx vite --version
npx vitest --run
npm install -D vitest@^5.0.0 @vitest/coverage-v8@^5.0.0
```

If you use Browser Mode, add `@vitest/browser-playwright@^5.0.0` and `vitest-browser-vue@^3.0.0` to that install line, and add `@vitest/ui@^5.0.0` if you use Vitest UI. Vue Test Utils does not depend on the runner, so `mount` and `wrapper` code is untouched.

Run the suite and work through the failures in this order: unawaited assertions first (the error names the line), then mock history, then locators. Some changes fail no test, so check these by hand: `-t` patterns in your `package.json` scripts, and CI steps that read `json` or `junit` output from the terminal.

Upgrade now if your machines and CI already run Node 22.12 or newer: each fix above is a small edit, and most of the benchmark apps ran faster. If CI is still on Node 20, move Node first; Vitest 4.1 keeps working in the meantime.

## Upgrading a Nuxt project

Nuxt tests run through @nuxt/test-utils, which declares the Vitest versions it supports. Support arrived in three releases on 7 September: 4.3.0 added Vitest 5 support, 4.3.1 added `^5.0.0` to its peer range, and 4.3.2 fixed the test environment. Upgrade both together:

```
npm install -D vitest@^5.0.0 @nuxt/test-utils@^4.3.2
```

With 4.3.0 or older, npm refuses the install because the module's peer range still reads `^4.0.2`. Everything in the sections above applies inside a Nuxt test suite as well.

**Check it stuck**3 questions from the Vitest and Vue Test Utils quiz.

[**Practice now**](https://vuejs.school/learn/testing/vitest-and-vue-test-utils?tab=quiz)

**Was this helpful?**

## **Keep reading**

[Release notes · 6 min read**Vitest 5 release candidate: what's coming for Vue tests**](https://vuejs.school/community/blog/vitest-5-rc-whats-coming) [Deep dive · 6 min read**Async tests: nextTick, flushPromises and fake timers**](https://vuejs.school/community/blog/async-tests-nexttick-flushpromises-and-fake-timers)

```json
{"@context":"https://schema.org","@graph":[{"@id":"https://vuejs.school/#website","@type":"WebSite","description":"A free AI-instructed school for Vue.js. Short tutorials, runnable code and quizzes that adapt to you.","name":"vuejs.school","url":"https://vuejs.school/","publisher":{"@id":"https://vuejs.school/#identity"}},{"@id":"https://vuejs.school/community/blog/vitest-5-what-changed-for-vue-tests#webpage","@type":"WebPage","dateModified":"2026-10-27T19:31:40.253Z","datePublished":"2026-09-08","description":"Vitest 5.0 is faster, clears mocks before every test and fails unawaited assertions. What it changes for Vue tests, Browser Mode and Nuxt, and how to upgrade.","name":"Vitest 5: what changed for Vue tests","url":"https://vuejs.school/community/blog/vitest-5-what-changed-for-vue-tests","breadcrumb":{"@id":"https://vuejs.school/community/blog/vitest-5-what-changed-for-vue-tests#breadcrumb"},"isPartOf":{"@id":"https://vuejs.school/#website"},"potentialAction":[{"@type":"ReadAction","target":["https://vuejs.school/community/blog/vitest-5-what-changed-for-vue-tests"]}],"primaryImageOfPage":{"@id":"https://vuejs.school/#logo"}},{"@id":"https://vuejs.school/#identity","@type":"Organization","description":"A free AI-instructed school for Vue.js. Short tutorials, runnable code and quizzes that adapt to you.","name":"vuejs.school","url":"https://vuejs.school","logo":{"@id":"https://vuejs.school/#logo"}},{"@id":"https://vuejs.school/community/blog/vitest-5-what-changed-for-vue-tests#article","dateModified":"2026-10-27T19:31:40.253Z","datePublished":"2026-09-08","description":"Vitest 5.0 is faster, clears mocks before every test and fails unawaited assertions. What it changes for Vue tests, Browser Mode and Nuxt, and how to upgrade.","headline":"Vitest 5: what changed for Vue tests","inLanguage":"en","isAccessibleForFree":true,"thumbnailUrl":"https://vuejs.school/_og/d/a_~Vml0ZXN0IDU6IHdoYXQgY2hhbmdlZCBmb3IgVnVlIHRlc3RzIMK3IHZ1ZWpzLnNjaG9vbA%2Cc_OgCard.takumi%2Ctitle_~Vml0ZXN0IDU6IHdoYXQgY2hhbmdlZCBmb3IgVnVlIHRlc3Rz%2Cdescription_~Vml0ZXN0IDUuMCBpcyBmYXN0ZXIsIGNsZWFycyBtb2NrcyBiZWZvcmUgZXZlcnkgdGVzdCBhbmQgZmFpbHMgdW5hd2FpdGVkIGFzc2VydGlvbnMuIFdoYXQgaXQgY2hhbmdlcyBmb3IgVnVlIHRlc3RzLCBCcm93c2VyIE1vZGUgYW5kIE51eHQsIGFuZCBob3cgdG8gdXBncmFkZS4%2Ceyebrow_~UmVsZWFzZSBub3RlcyDCtyA2IG1pbiByZWFk%2Ctint_mint%2Cp_Ii9jb21tdW5pdHkvYmxvZy92aXRlc3QtNS13aGF0LWNoYW5nZWQtZm9yLXZ1ZS10ZXN0cyI%2Cs_r6QHe5pZ9JLiVoIi.png","@type":["Article","BlogPosting"],"articleSection":["Release notes"],"author":{"@id":"https://vuejs.school/#identity"},"image":{"@id":"https://vuejs.school/#/schema/image/1"},"isPartOf":{"@id":"https://vuejs.school/community/blog/vitest-5-what-changed-for-vue-tests#webpage"},"mainEntityOfPage":{"@id":"https://vuejs.school/community/blog/vitest-5-what-changed-for-vue-tests#webpage"},"publisher":{"@id":"https://vuejs.school/#identity"}},{"@id":"https://vuejs.school/community/blog/vitest-5-what-changed-for-vue-tests#breadcrumb","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","name":"Blog","item":"https://vuejs.school/community/blog","position":1},{"@type":"ListItem","name":"Vitest 5: what changed for Vue tests","position":2}]},{"@id":"https://vuejs.school/#logo","@type":"ImageObject","caption":"vuejs.school","contentUrl":"https://vuejs.school/apple-touch-icon.png","inLanguage":"en","url":"https://vuejs.school/apple-touch-icon.png"},{"@id":"https://vuejs.school/#/schema/image/1","@type":"ImageObject","contentUrl":"https://vuejs.school/_og/d/a_~Vml0ZXN0IDU6IHdoYXQgY2hhbmdlZCBmb3IgVnVlIHRlc3RzIMK3IHZ1ZWpzLnNjaG9vbA%2Cc_OgCard.takumi%2Ctitle_~Vml0ZXN0IDU6IHdoYXQgY2hhbmdlZCBmb3IgVnVlIHRlc3Rz%2Cdescription_~Vml0ZXN0IDUuMCBpcyBmYXN0ZXIsIGNsZWFycyBtb2NrcyBiZWZvcmUgZXZlcnkgdGVzdCBhbmQgZmFpbHMgdW5hd2FpdGVkIGFzc2VydGlvbnMuIFdoYXQgaXQgY2hhbmdlcyBmb3IgVnVlIHRlc3RzLCBCcm93c2VyIE1vZGUgYW5kIE51eHQsIGFuZCBob3cgdG8gdXBncmFkZS4%2Ceyebrow_~UmVsZWFzZSBub3RlcyDCtyA2IG1pbiByZWFk%2Ctint_mint%2Cp_Ii9jb21tdW5pdHkvYmxvZy92aXRlc3QtNS13aGF0LWNoYW5nZWQtZm9yLXZ1ZS10ZXN0cyI%2Cs_r6QHe5pZ9JLiVoIi.png","inLanguage":"en","url":"https://vuejs.school/_og/d/a_~Vml0ZXN0IDU6IHdoYXQgY2hhbmdlZCBmb3IgVnVlIHRlc3RzIMK3IHZ1ZWpzLnNjaG9vbA%2Cc_OgCard.takumi%2Ctitle_~Vml0ZXN0IDU6IHdoYXQgY2hhbmdlZCBmb3IgVnVlIHRlc3Rz%2Cdescription_~Vml0ZXN0IDUuMCBpcyBmYXN0ZXIsIGNsZWFycyBtb2NrcyBiZWZvcmUgZXZlcnkgdGVzdCBhbmQgZmFpbHMgdW5hd2FpdGVkIGFzc2VydGlvbnMuIFdoYXQgaXQgY2hhbmdlcyBmb3IgVnVlIHRlc3RzLCBCcm93c2VyIE1vZGUgYW5kIE51eHQsIGFuZCBob3cgdG8gdXBncmFkZS4%2Ceyebrow_~UmVsZWFzZSBub3RlcyDCtyA2IG1pbiByZWFk%2Ctint_mint%2Cp_Ii9jb21tdW5pdHkvYmxvZy92aXRlc3QtNS13aGF0LWNoYW5nZWQtZm9yLXZ1ZS10ZXN0cyI%2Cs_r6QHe5pZ9JLiVoIi.png"}]}
```