mirror of
https://github.com/vitest-dev/vitest.git
synced 2025-12-08 18:26:03 +00:00
480 lines
14 KiB
Markdown
480 lines
14 KiB
Markdown
---
|
||
title: Coverage | Guide
|
||
---
|
||
|
||
# Coverage
|
||
|
||
Vitest supports Native code coverage via [`v8`](https://v8.dev/blog/javascript-code-coverage) and instrumented code coverage via [`istanbul`](https://istanbul.js.org/).
|
||
|
||
## Coverage Providers
|
||
|
||
Both `v8` and `istanbul` support are optional. By default, `v8` will be used.
|
||
|
||
You can select the coverage tool by setting `test.coverage.provider` to `v8` or `istanbul`:
|
||
|
||
```ts [vitest.config.ts]
|
||
import { defineConfig } from 'vitest/config'
|
||
|
||
export default defineConfig({
|
||
test: {
|
||
coverage: {
|
||
provider: 'v8' // or 'istanbul'
|
||
},
|
||
},
|
||
})
|
||
```
|
||
|
||
When you start the Vitest process, it will prompt you to install the corresponding support package automatically.
|
||
|
||
Or if you prefer to install them manually:
|
||
|
||
::: code-group
|
||
```bash [v8]
|
||
npm i -D @vitest/coverage-v8
|
||
```
|
||
```bash [istanbul]
|
||
npm i -D @vitest/coverage-istanbul
|
||
```
|
||
:::
|
||
|
||
## V8 Provider
|
||
|
||
::: info
|
||
The description of V8 coverage below is Vitest specific and does not apply to other test runners.
|
||
Since `v3.2.0` Vitest has used [AST based coverage remapping](/blog/vitest-3-2#coverage-v8-ast-aware-remapping) for V8 coverage, which produces identical coverage reports to Istanbul.
|
||
|
||
This allows users to have the speed of V8 coverage with accuracy of Istanbul coverage.
|
||
:::
|
||
|
||
By default Vitest uses `'v8'` coverage provider.
|
||
This provider requires Javascript runtime that's implemented on top of [V8 engine](https://v8.dev/), such as NodeJS, Deno or any Chromium based browsers such as Google Chrome.
|
||
|
||
Coverage collection is performed during runtime by instructing V8 using [`node:inspector`](https://nodejs.org/api/inspector.html) and [Chrome DevTools Protocol](https://chromedevtools.github.io/devtools-protocol/tot/Profiler/) in browsers. User's source files can be executed as-is without any pre-instrumentation steps.
|
||
|
||
- ✅ Recommended option to use
|
||
- ✅ No pre-transpile step. Test files can be executed as-is.
|
||
- ✅ Faster execute times than Istanbul.
|
||
- ✅ Lower memory usage than Istanbul.
|
||
- ✅ Coverage report accuracy is as good as with Istanbul ([since Vitest `v3.2.0`](/blog/vitest-3-2#coverage-v8-ast-aware-remapping)).
|
||
- ⚠️ In some cases can be slower than Istanbul, e.g. when loading lots of different modules. V8 does not support limiting coverage collection to specific modules.
|
||
- ⚠️ There are some minor limitations set by V8 engine. See [`ast-v8-to-istanbul` | Limitations](https://github.com/AriPerkkio/ast-v8-to-istanbul?tab=readme-ov-file#limitations).
|
||
- ❌ Does not work on environments that don't use V8, such as Firefox or Bun. Or on environments that don't expose V8 coverage via profiler, such as Cloudflare Workers.
|
||
|
||
<div style="display: flex; flex-direction: column; align-items: center; padding: 2rem 0; max-width: 20rem;">
|
||
<Box>Test file</Box>
|
||
<ArrowDown />
|
||
<Box>Enable V8 runtime coverage collection</Box>
|
||
<ArrowDown />
|
||
<Box>Run file</Box>
|
||
<ArrowDown />
|
||
<Box>Collect coverage results from V8</Box>
|
||
<ArrowDown />
|
||
<Box>Remap coverage results to source files</Box>
|
||
<ArrowDown />
|
||
<Box>Coverage report</Box>
|
||
</div>
|
||
|
||
## Istanbul provider
|
||
|
||
[Istanbul code coverage tooling](https://istanbul.js.org/) has existed since 2012 and is very well battle-tested.
|
||
This provider works on any Javascript runtime as coverage tracking is done by instrumenting user's source files.
|
||
|
||
In practice, instrumenting source files means adding additional Javascript in user's files:
|
||
|
||
```js
|
||
// Simplified example of branch and function coverage counters
|
||
const coverage = { // [!code ++]
|
||
branches: { 1: [0, 0] }, // [!code ++]
|
||
functions: { 1: 0 }, // [!code ++]
|
||
} // [!code ++]
|
||
|
||
export function getUsername(id) {
|
||
// Function coverage increased when this is invoked // [!code ++]
|
||
coverage.functions['1']++ // [!code ++]
|
||
|
||
if (id == null) {
|
||
// Branch coverage increased when this is invoked // [!code ++]
|
||
coverage.branches['1'][0]++ // [!code ++]
|
||
|
||
throw new Error('User ID is required')
|
||
}
|
||
// Implicit else coverage increased when if-statement condition not met // [!code ++]
|
||
coverage.branches['1'][1]++ // [!code ++]
|
||
|
||
return database.getUser(id)
|
||
}
|
||
|
||
globalThis.__VITEST_COVERAGE__ ||= {} // [!code ++]
|
||
globalThis.__VITEST_COVERAGE__[filename] = coverage // [!code ++]
|
||
```
|
||
|
||
- ✅ Works on any Javascript runtime
|
||
- ✅ Widely used and battle-tested for over 13 years.
|
||
- ✅ In some cases faster than V8. Coverage instrumentation can be limited to specific files, as opposed to V8 where all modules are instrumented.
|
||
- ❌ Requires pre-instrumentation step
|
||
- ❌ Execution speed is slower than V8 due to instrumentation overhead
|
||
- ❌ Instrumentation increases file sizes
|
||
- ❌ Memory usage is higher than V8
|
||
|
||
<div style="display: flex; flex-direction: column; align-items: center; padding: 2rem 0; max-width: 20rem;">
|
||
<Box>Test file</Box>
|
||
<ArrowDown />
|
||
<Box>Pre‑instrumentation with Babel</Box>
|
||
<ArrowDown />
|
||
<Box>Run file</Box>
|
||
<ArrowDown />
|
||
<Box>Collect coverage results from Javascript scope</Box>
|
||
<ArrowDown />
|
||
<Box>Remap coverage results to source files</Box>
|
||
<ArrowDown />
|
||
<Box>Coverage report</Box>
|
||
</div>
|
||
|
||
## Coverage Setup
|
||
|
||
::: tip
|
||
All coverage options are listed in [Coverage Config Reference](/config/#coverage).
|
||
:::
|
||
|
||
To test with coverage enabled, you can pass the `--coverage` flag in CLI or set `coverage.enabled` in `vitest.config.ts`:
|
||
|
||
::: code-group
|
||
```json [package.json]
|
||
{
|
||
"scripts": {
|
||
"test": "vitest",
|
||
"coverage": "vitest run --coverage"
|
||
}
|
||
}
|
||
```
|
||
```ts [vitest.config.ts]
|
||
import { defineConfig } from 'vitest/config'
|
||
|
||
export default defineConfig({
|
||
test: {
|
||
coverage: {
|
||
enabled: true
|
||
},
|
||
},
|
||
})
|
||
```
|
||
:::
|
||
|
||
## Including and excluding files from coverage report
|
||
|
||
You can define what files are shown in coverage report by configuring [`coverage.include`](/config/#coverage-include) and [`coverage.exclude`](/config/#coverage-exclude).
|
||
|
||
By default Vitest will show only files that were imported during test run.
|
||
To include uncovered files in the report, you'll need to configure [`coverage.include`](/config/#coverage-include) with a pattern that will pick your source files:
|
||
|
||
::: code-group
|
||
```ts [vitest.config.ts] {6}
|
||
import { defineConfig } from 'vitest/config'
|
||
|
||
export default defineConfig({
|
||
test: {
|
||
coverage: {
|
||
include: ['src/**.{ts,tsx}']
|
||
},
|
||
},
|
||
})
|
||
```
|
||
```sh [Covered Files]
|
||
├── src
|
||
│ ├── components
|
||
│ │ └── counter.tsx # [!code ++]
|
||
│ ├── mock-data
|
||
│ │ ├── products.json # [!code error]
|
||
│ │ └── users.json # [!code error]
|
||
│ └── utils
|
||
│ ├── formatters.ts # [!code ++]
|
||
│ ├── time.ts # [!code ++]
|
||
│ └── users.ts # [!code ++]
|
||
├── test
|
||
│ └── utils.test.ts # [!code error]
|
||
│
|
||
├── package.json # [!code error]
|
||
├── tsup.config.ts # [!code error]
|
||
└── vitest.config.ts # [!code error]
|
||
```
|
||
:::
|
||
|
||
To exclude files that are matching `coverage.include`, you can define an additional [`coverage.exclude`](/config/#coverage-exclude):
|
||
|
||
::: code-group
|
||
```ts [vitest.config.ts] {7}
|
||
import { defineConfig } from 'vitest/config'
|
||
|
||
export default defineConfig({
|
||
test: {
|
||
coverage: {
|
||
include: ['src/**.{ts,tsx}'],
|
||
exclude: ['**/utils/users.ts']
|
||
},
|
||
},
|
||
})
|
||
```
|
||
```sh [Covered Files]
|
||
├── src
|
||
│ ├── components
|
||
│ │ └── counter.tsx # [!code ++]
|
||
│ ├── mock-data
|
||
│ │ ├── products.json # [!code error]
|
||
│ │ └── users.json # [!code error]
|
||
│ └── utils
|
||
│ ├── formatters.ts # [!code ++]
|
||
│ ├── time.ts # [!code ++]
|
||
│ └── users.ts # [!code error]
|
||
├── test
|
||
│ └── utils.test.ts # [!code error]
|
||
│
|
||
├── package.json # [!code error]
|
||
├── tsup.config.ts # [!code error]
|
||
└── vitest.config.ts # [!code error]
|
||
```
|
||
:::
|
||
|
||
## Custom Coverage Reporter
|
||
|
||
You can use custom coverage reporters by passing either the name of the package or absolute path in `test.coverage.reporter`:
|
||
|
||
```ts [vitest.config.ts]
|
||
import { defineConfig } from 'vitest/config'
|
||
|
||
export default defineConfig({
|
||
test: {
|
||
coverage: {
|
||
reporter: [
|
||
// Specify reporter using name of the NPM package
|
||
['@vitest/custom-coverage-reporter', { someOption: true }],
|
||
|
||
// Specify reporter using local path
|
||
'/absolute/path/to/custom-reporter.cjs',
|
||
],
|
||
},
|
||
},
|
||
})
|
||
```
|
||
|
||
Custom reporters are loaded by Istanbul and must match its reporter interface. See [built-in reporters' implementation](https://github.com/istanbuljs/istanbuljs/tree/master/packages/istanbul-reports/lib) for reference.
|
||
|
||
```js [custom-reporter.cjs]
|
||
const { ReportBase } = require('istanbul-lib-report')
|
||
|
||
module.exports = class CustomReporter extends ReportBase {
|
||
constructor(opts) {
|
||
super()
|
||
|
||
// Options passed from configuration are available here
|
||
this.file = opts.file
|
||
}
|
||
|
||
onStart(root, context) {
|
||
this.contentWriter = context.writer.writeFile(this.file)
|
||
this.contentWriter.println('Start of custom coverage report')
|
||
}
|
||
|
||
onEnd() {
|
||
this.contentWriter.println('End of custom coverage report')
|
||
this.contentWriter.close()
|
||
}
|
||
}
|
||
```
|
||
|
||
## Custom Coverage Provider
|
||
|
||
It's also possible to provide your custom coverage provider by passing `'custom'` in `test.coverage.provider`:
|
||
|
||
```ts [vitest.config.ts]
|
||
import { defineConfig } from 'vitest/config'
|
||
|
||
export default defineConfig({
|
||
test: {
|
||
coverage: {
|
||
provider: 'custom',
|
||
customProviderModule: 'my-custom-coverage-provider'
|
||
},
|
||
},
|
||
})
|
||
```
|
||
|
||
The custom providers require a `customProviderModule` option which is a module name or path where to load the `CoverageProviderModule` from. It must export an object that implements `CoverageProviderModule` as default export:
|
||
|
||
```ts [my-custom-coverage-provider.ts]
|
||
import type {
|
||
CoverageProvider,
|
||
CoverageProviderModule,
|
||
ResolvedCoverageOptions,
|
||
Vitest
|
||
} from 'vitest'
|
||
|
||
const CustomCoverageProviderModule: CoverageProviderModule = {
|
||
getProvider(): CoverageProvider {
|
||
return new CustomCoverageProvider()
|
||
},
|
||
|
||
// Implements rest of the CoverageProviderModule ...
|
||
}
|
||
|
||
class CustomCoverageProvider implements CoverageProvider {
|
||
name = 'custom-coverage-provider'
|
||
options!: ResolvedCoverageOptions
|
||
|
||
initialize(ctx: Vitest) {
|
||
this.options = ctx.config.coverage
|
||
}
|
||
|
||
// Implements rest of the CoverageProvider ...
|
||
}
|
||
|
||
export default CustomCoverageProviderModule
|
||
```
|
||
|
||
Please refer to the type definition for more details.
|
||
|
||
## Ignoring Code
|
||
|
||
Both coverage providers have their own ways how to ignore code from coverage reports:
|
||
|
||
- [`v8`](https://github.com/AriPerkkio/ast-v8-to-istanbul?tab=readme-ov-file#ignoring-code)
|
||
- [`istanbul`](https://github.com/istanbuljs/nyc#parsing-hints-ignoring-lines)
|
||
|
||
When using TypeScript the source codes are transpiled using `esbuild`, which strips all comments from the source codes ([esbuild#516](https://github.com/evanw/esbuild/issues/516)).
|
||
Comments which are considered as [legal comments](https://esbuild.github.io/api/#legal-comments) are preserved.
|
||
|
||
You can include a `@preserve` keyword in the ignore hint.
|
||
Beware that these ignore hints may now be included in final production build as well.
|
||
|
||
```diff
|
||
-/* istanbul ignore if */
|
||
+/* istanbul ignore if -- @preserve */
|
||
if (condition) {
|
||
|
||
-/* v8 ignore if */
|
||
+/* v8 ignore if -- @preserve */
|
||
if (condition) {
|
||
```
|
||
|
||
### Examples
|
||
|
||
::: code-group
|
||
|
||
```ts [if else]
|
||
/* v8 ignore if -- @preserve */
|
||
if (parameter) { // [!code error]
|
||
console.log('Ignored') // [!code error]
|
||
} // [!code error]
|
||
else {
|
||
console.log('Included')
|
||
}
|
||
|
||
/* v8 ignore else -- @preserve */
|
||
if (parameter) {
|
||
console.log('Included')
|
||
}
|
||
else { // [!code error]
|
||
console.log('Ignored') // [!code error]
|
||
} // [!code error]
|
||
```
|
||
|
||
```ts [next node]
|
||
/* v8 ignore next -- @preserve */
|
||
console.log('Ignored') // [!code error]
|
||
console.log('Included')
|
||
|
||
/* v8 ignore next -- @preserve */
|
||
function ignored() { // [!code error]
|
||
console.log('all') // [!code error]
|
||
// [!code error]
|
||
console.log('lines') // [!code error]
|
||
// [!code error]
|
||
console.log('are') // [!code error]
|
||
// [!code error]
|
||
console.log('ignored') // [!code error]
|
||
} // [!code error]
|
||
|
||
/* v8 ignore next -- @preserve */
|
||
class Ignored { // [!code error]
|
||
ignored() {} // [!code error]
|
||
alsoIgnored() {} // [!code error]
|
||
} // [!code error]
|
||
|
||
/* v8 ignore next -- @preserve */
|
||
condition // [!code error]
|
||
? console.log('ignored') // [!code error]
|
||
: console.log('also ignored') // [!code error]
|
||
```
|
||
|
||
```ts [try catch]
|
||
/* v8 ignore next -- @preserve */
|
||
try { // [!code error]
|
||
console.log('Ignored') // [!code error]
|
||
} // [!code error]
|
||
catch (error) { // [!code error]
|
||
console.log('Ignored') // [!code error]
|
||
} // [!code error]
|
||
|
||
try {
|
||
console.log('Included')
|
||
}
|
||
catch (error) {
|
||
/* v8 ignore next -- @preserve */
|
||
console.log('Ignored') // [!code error]
|
||
/* v8 ignore next -- @preserve */
|
||
console.log('Ignored') // [!code error]
|
||
}
|
||
|
||
// Requires rolldown-vite due to esbuild's lack of support.
|
||
// See https://vite.dev/guide/rolldown.html#how-to-try-rolldown
|
||
try {
|
||
console.log('Included')
|
||
}
|
||
catch (error) /* v8 ignore next */ { // [!code error]
|
||
console.log('Ignored') // [!code error]
|
||
} // [!code error]
|
||
```
|
||
|
||
```ts [switch case]
|
||
switch (type) {
|
||
case 1:
|
||
return 'Included'
|
||
|
||
/* v8 ignore next -- @preserve */
|
||
case 2: // [!code error]
|
||
return 'Ignored' // [!code error]
|
||
|
||
case 3:
|
||
return 'Included'
|
||
|
||
/* v8 ignore next -- @preserve */
|
||
default: // [!code error]
|
||
return 'Ignored' // [!code error]
|
||
}
|
||
```
|
||
|
||
```ts [whole file]
|
||
/* v8 ignore file -- @preserve */
|
||
export function ignored() { // [!code error]
|
||
return 'Whole file is ignored'// [!code error]
|
||
}// [!code error]
|
||
```
|
||
:::
|
||
|
||
## Coverage Performance
|
||
|
||
If code coverage generation is slow on your project, see [Profiling Test Performance | Code coverage](/guide/profiling-test-performance.html#code-coverage).
|
||
|
||
## Vitest UI
|
||
|
||
You can check your coverage report in [Vitest UI](/guide/ui).
|
||
|
||
Vitest UI will enable coverage report when it is enabled explicitly and the html coverage reporter is present, otherwise it will not be available:
|
||
- enable `coverage.enabled=true` in your configuration file or run Vitest with `--coverage.enabled=true` flag
|
||
- add `html` to the `coverage.reporter` list: you can also enable `subdir` option to put coverage report in a subdirectory
|
||
|
||
<img alt="html coverage activation in Vitest UI" img-light src="/vitest-ui-show-coverage-light.png">
|
||
<img alt="html coverage activation in Vitest UI" img-dark src="/vitest-ui-show-coverage-dark.png">
|
||
|
||
<img alt="html coverage in Vitest UI" img-light src="/ui-coverage-1-light.png">
|
||
<img alt="html coverage in Vitest UI" img-dark src="/ui-coverage-1-dark.png">
|