---
url: /guide/learn/debugging-tests.md
---

# Debugging Failing Tests

This page covers how to investigate test failures in Vitest: reading error output, isolating problems, identifying common causes, and using the available debugging tools.

## Reading the Error

When a test fails, Vitest gives you several pieces of information. Let's look at a real failure and break it down:

```ansi
 [31mFAIL[39m src/user.test.js [2m>[22m createUser [2m>[22m sets the default role
[31mAssertionError: expected { name: 'Alice', role: 'viewer' } to deeply equal { name: 'Alice', role: 'member' }[39m

[31m- Expected[39m
[32m+ Received[39m

  {
    "name": "Alice",
[31m-   "role": "member",[39m
[32m+   "role": "viewer",[39m
  }

 [2m❯[22m [36msrc/user.test.js[39m[2m:8:22[22m
[2m      6|[22m   [34mtest[39m([32m'sets the default role'[39m, () => {
[2m      7|[22m     [35mconst[39m user = [34mcreateUser[39m([32m'Alice'[39m)
[2m      8|[22m     [34mexpect[39m(user).[34mtoEqual[39m({ name: [32m'Alice'[39m, role: [32m'member'[39m })
                          [31m^[39m
[2m      9|[22m   })
[2m     10|[22m })

```

There's a lot here, but each part tells you something:

**The header** (`FAIL src/user.test.js > createUser > sets the default role`) tells you which file, describe block, and test failed. This is the full path in the test tree.

**The assertion message** (`expected { ... } to deeply equal { ... }`) tells you what kind of check failed and shows the two values being compared.

**The diff** shows exactly what's different. Lines starting with + are what you actually got, and lines starting with - are what you expected. In this case, the role was "viewer" but the test expected "member".

**The code snippet** shows the exact line and a few surrounding lines, with a caret (`^`) pointing to the failing assertion. You can click the file path in most terminals and IDEs to jump directly there.

At this point, the question is: did the code change (maybe the default role was intentionally updated to `"viewer"`), or is the test wrong? Check the source code for `createUser` to find out. If the default was intentionally changed, update the test. If not, you've found a bug.

## Isolating the Problem

When a test fails and the cause isn't immediately clear, the first step is to isolate it. Run just that one test, without the rest of your suite:

```bash
# Run only the failing test file
vitest src/user.test.js

# Run only tests matching a name pattern
vitest -t "sets the default role"

# Combine both for maximum precision
vitest src/user.test.js -t "sets the default role"
```

You can also add [`.only`](/api/test#only) to the test itself:

```js
test.only('sets the default role', () => {
  // only this test runs in the file
})
```

If you have many failures and want to focus on the first one, use [`--bail`](/config/bail) to stop after a set number of failures:

```bash
vitest --bail 1
```

If the test passes when run alone but fails when run with others, you have a test isolation problem (more on that below). If it fails even when run alone, the issue is in the test itself or the code it's testing.

## Common Causes of Failures

### Shared State Between Tests

This is one of the most common and frustrating issues. A test passes when you run it alone, but fails when the full suite runs. The usual cause is that some other test modifies shared state (a global variable, a module-level cache, a database) and doesn't clean up after itself.

```js
// This is a problem: `users` is shared between tests
const users = []

test('adds a user', () => {
  users.push('Alice')
  expect(users).toEqual(['Alice'])
})

test('starts empty', () => {
  // This fails because 'Alice' is still in the array!
  expect(users).toEqual([])
})
```

The fix is to reset the state before each test with [`beforeEach`](/api/hooks#beforeeach), or better yet, use [`test.extend`](/guide/test-context#extend-test-context) to create fresh state for each test automatically:

```js
const test = baseTest.extend('users', () => [])

test('adds a user', ({ users }) => {
  users.push('Alice')
  expect(users).toEqual(['Alice'])
})

test('starts empty', ({ users }) => {
  // Passes: each test gets its own array
  expect(users).toEqual([])
})
```

### Async Issues

Tests that involve promises can fail intermittently or in confusing ways if the async flow isn't handled correctly. The most common mistake is forgetting an `await`:

```js
// This test always passes, even if fetchUser rejects!
test('fetches user', () => {
  // Missing await: the test finishes before the promise settles
  expect(fetchUser(1)).resolves.toMatchObject({ name: 'Alice' })
})
```

Vitest will usually warn you about unawaited assertions at the end of the test. If you see that warning, add the missing `await`:

```js
test('fetches user', async () => {
  await expect(fetchUser(1)).resolves.toMatchObject({ name: 'Alice' })
})
```

If a test hangs and eventually times out, it usually means a promise never resolves. Check for missing callbacks, unresolved conditions, or deadlocks in the code you're testing.

### Stale Snapshots

If you're using [snapshot tests](/guide/learn/snapshots) and you intentionally changed the output of your code, the existing snapshots will be outdated. The test fails and shows a diff between the old snapshot and the new output.

This is expected. Review the diff to confirm the changes are correct, then update the snapshots by pressing `u` in watch mode or running `vitest -u`.

### Wrong Test Environment

If your code accesses browser APIs like `document` or `window` and you see errors like "document is not defined", your test is running in the Node environment (the default). You can switch to a browser-like environment with the [`environment`](/config/environment) config option, or better yet, use [Browser Mode](/guide/browser/) which runs tests in a real browser.

### Mocks Not Cleaned Up

If a mock from one test leaks into another, you'll get unexpected behavior. For example, a `vi.spyOn` that overrides a method's return value will persist into the next test unless it's restored.

The easiest fix is to enable automatic mock restoration in your config:

```js [vitest.config.js]
import { defineConfig } from 'vitest/config'

export default defineConfig({
  test: {
    restoreMocks: true,
  },
})
```

This calls [`mockRestore()`](/api/mock#mockrestore) on every mock after each test. See the [Mock Functions](/guide/learn/mock-functions#resetting-mocks) tutorial for more details.

## Debugging Tools

### Console Logging

There's nothing wrong with adding `console.log` to your tests. It's the fastest way to inspect values and understand what's happening:

```js
test('transforms data correctly', () => {
  const input = getData()
  console.log('input:', input)

  const result = transform(input)
  console.log('result:', result)

  expect(result).toMatchObject({ status: 'ok' })
})
```

Vitest displays console output inline with the test results, so you can see which test produced which log.

### Vitest UI

For a visual overview of your test suite, run Vitest with the `--ui` flag:

```bash
vitest --ui
```

This opens a browser-based dashboard where you can see all your tests, their status, and their output. It also includes a module graph that shows how your files are connected, which can help you understand why a change in one file causes failures in another. See the [Vitest UI](/guide/ui) guide for more details.

### VS Code Extension

The [Vitest VS Code extension](https://vitest.dev/vscode) lets you run and debug individual tests directly from your editor. You can click a "play" button next to any test, set breakpoints, and step through code in the VS Code debugger. This is often faster than switching between the terminal and your editor.

### Verbose Output

If the default output isn't showing enough detail, use the verbose reporter:

```bash
vitest --reporter=verbose
```

This shows every test individually (not just the files), which can help spot patterns in which tests pass and which fail.

### Attaching a Debugger

For more complex issues where you need to step through code line by line, you can run Vitest with the `--inspect-brk` flag and attach a debugger. The `--no-file-parallelism` flag ensures tests run in the main thread so breakpoints work reliably:

```bash
vitest --inspect-brk --no-file-parallelism
```

Then attach from VS Code, IntelliJ, or Chrome DevTools (`chrome://inspect`). See the [Debugging](/guide/debugging) guide for detailed setup instructions for each editor.

## Getting Help

If you're stuck, these resources can help:

* The [Common Errors](/guide/common-errors) page covers specific error messages and their solutions
* [GitHub Issues](https://github.com/vitest-dev/vitest/issues) for searching known bugs and workarounds
* The [Discord community](https://chat.vitest.dev) for real-time help from other Vitest users and maintainers
