---
url: https://huldoser.github.io/test-inventory/frameworks/vitest.md
description: >-
  Scan Vitest tests without running them, interpret the inventory, and check
  Vitest syntax and limitations.
---

# Vitest

This guide covers **Vitest 3.0 or newer**. You need Node.js 22.12 or newer to run test-inventory and an
existing JavaScript or TypeScript test suite. The scanner reads your source without executing your tests or starting
a browser. These are minimum supported versions; [CI comparisons](/guide/runners#checked-in-ci) list tested versions.

[Choose another framework](/frameworks/) · [Use an AI client](/guide/agents)

## Install

```sh
npm install --save-dev test-inventory
```

## Scan your suite

Run from the folder containing your project's `package.json`:

```sh
npx test-inventory --framework vitest --pretty --output inventory.json
```

To select files explicitly, use quoted globs. This example matches the `order-service` fixture used below;
adjust the globs to your suite's layout:

```sh
npx test-inventory "tests/**/*.test.ts" --framework vitest --output inventory.json
```

The command replaces `inventory.json` once the new output is complete. It does not change your test files.

## Check the result

Print the counts and diagnostics from the saved file:

```sh
node -e "const i = require('./inventory.json'); console.log(i.summary); console.log(i.diagnostics)"
```

For matching tests, `summary.fileCount` and `summary.testCount` should be greater than zero. Review diagnostics
even when the command exits with 0: errors can mean tests are missing. If the result is empty or unexpected,
use [troubleshooting](/guide/troubleshooting).

The inventory describes source declarations, not execution results. Conditional skips are not proof that a run
skipped the test. Loops and tables are one record by default; supported cases can be expanded with `--expand-cases`.
Expected-to-fail tests execute and check for failure; they are not disabled tests.

Tests created by unreadable helpers can be missing without a diagnostic. A dependency hash does not prove complete
coverage of runtime dependencies. Read the [shared limits](/guide/limits) before using scans to make CI decisions.

## Find skipped tests

```sh
npx test-inventory --framework vitest --state skip --fields fullName,state,condition,reason --pretty
```

This uses the same file selection as the first scan. If you supplied globs, `--root` or `--framework-version`,
repeat those options here.

This selects source declarations marked skip; it is not a report of a test run.
See [query recipes](/guide/recipes) for other inventory queries.

## Example

`order-service` is an HTTP service that sends orders to a broker, tested with Vitest. Its repository tests need a
database and skip themselves without one:

```ts excerpt
// tests/repository/orders-repository.test.ts
describe.skipIf(!process.env.DATABASE_URL)('orders repository', { tags: ['db'] }, () => {
  // ...
  test('stores an order with its broker id', async () => {
```

```sh
npx test-inventory "tests/**/*.test.ts" --framework vitest --output inventory.json
```

Each test in the describe gets the describe's condition and tags. `stateSource` and `stateLine` say where the state
comes from:

```json
{
  "fullName": "orders repository > stores an order with its broker id",
  "state": "skip",
  "isConditional": true,
  "condition": "!process.env.DATABASE_URL",
  "stateSource": "suite",
  "stateLine": 5,
  "tags": ["db"]
}
```

The project's Vitest config defines the `db` tag with only a description, so the tag changes nothing else about these
tests. A tag the config doesn't define would fail the file, and the scan would report it as `undefined-tag`.

A skip from the test context is read as well, with its condition and note:

```ts excerpt
// tests/risk/daily-loss-limit.test.ts
  test('resets the loss at the start of the next session', ({ skip }) => {
    skip(process.env.TZ !== 'America/New_York', 'session boundaries are checked in New York time');
```

```json
{
  "fullName": "daily loss limit > resets the loss at the start of the next session",
  "state": "skip",
  "condition": "process.env.TZ !== 'America/New_York'",
  "reason": "session boundaries are checked in New York time",
  "stateSource": "test",
  "stateLine": 14,
  "isParallel": true
}
```

A known bug marked with `test.fails` is `expectedToFail`, and the comment above it is kept:

```ts excerpt
// tests/broker/fill-events.test.ts
  // BUG: TRD-588 fractional partial fills are rounded down to whole shares
  test.fails('keeps the fractional shares of a partial fill', () => {
```

```json
{
  "fullName": "filledShares > keeps the fractional shares of a partial fill",
  "state": "expectedToFail",
  "comments": ["BUG: TRD-588 fractional partial fills are rounded down to whole shares"]
}
```

Other Vitest examples: [React](/frameworks/vitest-examples#react-with-testing-library), [Vue](/frameworks/vitest-examples#vue-with-vue-test-utils), [browser mode](/frameworks/vitest-examples#vitest-browser-mode), [in-source and type tests](/frameworks/vitest-examples#in-source-and-type-tests), [monorepos](/frameworks/vitest-examples#a-monorepo).

## Configuration and limits

The scan finds `vitest.config.*`, or `vite.config.*` when absent, in the test file's folder and its ancestors.
A config elsewhere that reaches the file is not found from it. It reads file and project selection,
`test.globals`, run settings, modes, `allowOnly` and defined tags. A Vitest 3 project list in
`vitest.workspace.*` or `test.workspace` is not read, so the config is taken to run the file.

A tag whose definition cannot be worked out produces a `config-may-change-state` warning on files using tags:
the tag may skip a test or change its settings. `vi.setConfig()` and `annotate()` in hooks are not read.

`resolve.alias` and `test.alias` are followed for imports and dependency hashes. `mergeConfig` puts overriding
list aliases first, converting object aliases to lists when either side is a list; two objects keep overriding
values. Config paths built with string `+` or a template over `__dirname` or `import.meta.dirname` are read.

### Tables and case titles

Vitest's titles follow the rules of the project's Vitest version: `%s`, `%d`, `%i`, `%f`, `%j`, `%o`, `%O`, `%c`, `%#`,
`%$` and `$name`, with values cut as the config of the project that runs the file cuts them:
`taskTitleValueFormatTruncate`, or before Vitest 5 `chaiConfig.truncateThreshold`, and 40 characters, Vitest's default,
when it sets neither. A table stays one record, with a `cases-not-expanded` warning, when the scan can't work out a
value in it or its title, or the title would show an array or object.

Loops over `var` are not expanded because the variable holds its last value by the time Vitest runs describes.
Include source files when scanning in-source tests, and `*.test-d.ts` files for type tests. See the
[in-source and type-test example](/frameworks/vitest-examples#in-source-and-type-tests).

### Mocks, snapshots and dependency hashes

Automatic mocks in `__mocks__` are not followed. A module named by `vi.mock()` enters the hash even when a mock
replaces it. Nondefault snapshot paths from `resolveSnapshotPath`, `snapshotEnvironment` or browser screenshot
settings give snapshot tests a `dependency-not-followed` warning at that setting. Unreadable projects, such as a
Vitest 3 workspace, mark tests with `hasUnfollowedDependencies`; the config's warning identifies the cause.

Vitest keeps one `.snap` file for a spec, so changing one snapshot changes the hash of every test in the spec
using `toMatchSnapshot()`. A custom matcher calling `Snapshots.toMatchSnapshot.call(this, received)` includes
the snapshot file for every test that reaches it.

::: details Exact file selection

For Vitest: the
files under `test.dir`, `test.root` or `root` that `include` matches and `exclude` doesn't, with each version's
defaults, `includeSource` files that hold in-source tests, `typecheck.include` when `typecheck.enabled` is on, and
`benchmark.include`, whose files are marked `isBenchmark`. A project written in `test.projects` inherits the root config
as each version decides, or the config its `extends` names. It runs from the config's root unless it sets its own, which
Vitest 5 resolves from the config's root and older versions from the config's folder. A config file or folder it names
is a project of its own, a folder without a config gets Vitest's defaults, and in Vitest 5 a config that lists projects
runs only those.

:::

## Supported syntax

Vitest 3.0 and newer.

### Where test comes from

| Form                       | Example                                                                                                                                                                                                                                                                                                                                                                                                                |
| -------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Named import, also renamed | `import { test, it, describe, suite } from 'vitest'`                                                                                                                                                                                                                                                                                                                                                                   |
| Namespace import           | `import * as vt from 'vitest'` then `vt.test(...)`                                                                                                                                                                                                                                                                                                                                                                     |
| Globals                    | `describe`, `it`, `test` and `suite` used without an import or declaration. A warning says when no config turns globals on.                                                                                                                                                                                                                                                                                            |
| In-source tests            | `const { it, describe } = import.meta.vitest`                                                                                                                                                                                                                                                                                                                                                                          |
| Describe callback          | `describe('fees', (test) => { test(...) })`: Vitest passes its `test` to the callback                                                                                                                                                                                                                                                                                                                                  |
| Derived                    | `test.extend({...})`, `test.extend('name', value)`, chained; `test.override()` and `test.scoped()` are known                                                                                                                                                                                                                                                                                                           |
| Local fixtures file        | `import { test } from './context'`, or a path alias, `#` import or workspace package, followed through re-exports, `extend` chains and functions that build it, across files                                                                                                                                                                                                                                           |
| Returned by a function     | `onNode(22)('reads the order book', ...)`, also kept in a variable, where `onNode` is declared in the file or a local file and returns a test or describe function, as in `it.skip`                                                                                                                                                                                                                                    |
| Destructured members       | `const { describe, skip, beforeEach } = test`: each works as `test.describe` or `test.skip` does. Some members read the function they are called on, so a call through one destructured fails the file, reported as `invalid-chain`: `each`, `for`, `skipIf` and `runIf` of `test` and `each` of `describe` in every version, `extend`, `scoped` and `override` from Vitest 4.1, and `for` of `describe` from Vitest 5 |
| Kept in a variable         | `const onCi = process.env.CI ? test.skip : test`: only what both branches share applies, also to a choice written in place, as `(process.env.CI ? test.skip : test)(...)`                                                                                                                                                                                                                                              |

### Calls

| Call                                                                                                       | Read as                                                                                                                     |
| ---------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
| `test(title, fn)`, `test(title, options, fn)`, `test(title, fn, timeout)`                                  | A test. Without a function, a todo.                                                                                         |
| `test(title, fn, options)`                                                                                 | Accepted before Vitest 4; a `removed-api` error from Vitest 4.                                                              |
| `.concurrent`, `.only`, `.skip`, `.todo`, `.fails`, before a called `.each` or `.for`                      | `.only` wins over `.skip`, which wins over `.todo`.                                                                         |
| `.each(table)`, `.for(table)`, also as tagged templates                                                    | One parameterized record, with `caseCount` when the scan can work out the table; one record per case with `--expand-cases`. |
| `.skipIf(condition)`, `.runIf(condition)`                                                                  | A conditional skip. `runIf(x)` is stored as the condition `!(x)`.                                                           |
| `describe` with `.concurrent`, `.shuffle`, `.skip`, `.only`, `.todo`, `.each`, `.for`, `.skipIf`, `.runIf` | A describe. `describe(title)` without a function is a todo.                                                                 |
| `describe(fn)` without a title                                                                             | A `describe-not-called` error: Vitest never calls the function.                                                             |
| `context.skip()`, `skip(note)`, `skip(condition, note)` in a test, `beforeEach` or `aroundEach`            | A state for that test, or for the tests in the describe.                                                                    |
| Options `skip`, `only`, `todo`, `fails`, `concurrent`, `sequential`, `shuffle`, `tags` (4.1), `meta`       | Read when written in the file; a test's own states win over its chain.                                                      |
| `// @module-tag name`                                                                                      | A tag on every test in the file, from Vitest 4.1.                                                                           |
| `.sequential`                                                                                              | The default mode before Vitest 5; a `removed-api` error from Vitest 5.                                                      |
| `bench(...)`, with `bench` imported from `vitest`                                                          | A `removed-api` error from Vitest 5. Benchmarks are never tests.                                                            |
| `vi.mock`, `vi.unmock`, `vi.hoisted` below the top level of the file                                       | A `removed-api` error from Vitest 5, except in files with in-source tests.                                                  |
| Any other chain, such as `test.fixme`                                                                      | An `invalid-chain` error.                                                                                                   |

The [shared syntax rules](/reference/syntax#in-every-runner) also apply.

## Next

* [Shared limits](/guide/limits): imports, generated tests, conditions and dependency coverage.
* [Key concepts](/guide/concepts): counts, states, IDs and hashes.
* [Output reference](/reference/output) and [diagnostics](/reference/diagnostics): interpret every field and warning.
* [CLI](/guide/cli), [library](/guide/library) and [MCP](/guide/agents): use the same inventory through each interface.
* [Compare with Vitest](/guide/runners#vitest-5-s-static-list): differences from runner collection and execution reports.
