LEARN · DEBUGGING GUIDE

Vitest Coverage Not Collecting: Debugging Empty or Missing Coverage Reports

If your Vitest coverage report shows zeros or no data, the problem is almost always a misconfigured coverage provider, missing instrumented files, or test files excluded from the report. This guide walks you through the exact steps to find and fix the root cause.

IntermediateTesting7 min read

What this usually means

Vitest coverage relies on a provider (c8 or istanbul) to instrument source files during test execution. The most common cause is that the source files are not being instrumented because they are excluded from the coverage configuration (e.g., using `all: false` or incorrect `include` patterns) or because the provider is not properly configured. Another frequent issue is that the coverage provider is set to 'v8' but the Node.js version doesn't support it, or the provider is missing from dependencies. Additionally, if your test files are in a separate directory but source files are in `src/`, the default coverage configuration may only look at test files. The underlying theme: the coverage collector doesn't know which files to instrument, or the instrumentation itself fails silently.

( 01 )Fast diagnosis

The first ten minutes — establish facts before touching code.

  • 1Run `npx vitest --coverage` with `--reporter=verbose` to see which files are being instrumented.
  • 2Check the `coverage` section in `vitest.config.ts` — ensure `provider` is set to `'c8'` or `'istanbul'` and that `all: true` is set if you want source files outside test files.
  • 3Verify that `coverage.include` and `coverage.exclude` patterns match your source files: run `npx vitest --coverage` and look for 'Coverage data for ...' in the output.
  • 4Check if the coverage provider package is installed: `npm ls @vitest/coverage-c8` or `npm ls @vitest/coverage-istanbul` depending on your config.
  • 5Inspect the generated HTML report: open `coverage/index.html` and see if the file tree shows your source files; if not, the collector didn't find them.
  • 6Run `node -e "console.log(process.versions.node)"` to confirm Node.js version >=14 if using c8 (v8 coverage).
( 02 )Where to look

The specific files, logs, configs, and dashboards that usually own this bug.

  • search`vitest.config.ts` (or `vitest.config.js`) — the `coverage` object with `provider`, `all`, `include`, `exclude`.
  • search`package.json` — ensure `@vitest/coverage-c8` or `@vitest/coverage-istanbul` is in `devDependencies`.
  • search`coverage/` directory — check if files exist, especially `index.html` and `lcov.info`.
  • searchTest output in terminal — look for 'No coverage data collected' or 'Coverage data for ...' lines.
  • search`.gitignore` — verify `coverage/` is not ignored if you expect it in version control.
  • searchCI pipeline logs — compare coverage output with local; often CI has different working directories or env vars.
( 03 )Common root causes

Practical causes, not theory. These are the things you will actually find.

  • warning`coverage.all` is `false` (default) — only files imported by tests are instrumented; source files not imported are excluded.
  • warningWrong `coverage.provider` — e.g., set to `'v8'` but `@vitest/coverage-v8` is not installed or Node.js <14.
  • warning`coverage.include` patterns too narrow — e.g., `['src/**/*.ts']` but your source is in `lib/`.
  • warning`coverage.exclude` inadvertently covers source files — e.g., `['**/*.test.ts']` but also `['src/**/*.spec.ts']` if tests are colocated.
  • warningMissing `@vitest/coverage-c8` or `@vitest/coverage-istanbul` dependency — Vitest falls back to no-op.
  • warningTest files and source files are in the same directory with same extension, and `include` uses a pattern that only matches test files.
  • warningEnvironment variable `NODE_V8_COVERAGE` is set but interfering with c8's internal logic (rare).
( 04 )Fix patterns

Concrete fix directions. Pick the one that matches your root cause.

  • buildSet `coverage.all: true` in `vitest.config.ts` to instrument all source files matching `include` patterns, regardless of whether tests import them.
  • buildExplicitly set `coverage.include` to your source directory: `['src/**/*.ts']` and ensure `coverage.exclude` does not exclude them.
  • buildInstall the correct provider: `npm install -D @vitest/coverage-c8` for c8 or `@vitest/coverage-istanbul` for istanbul.
  • buildIf using `coverage.provider: 'c8'`, ensure Node.js version >=14 and that `@vitest/coverage-c8` is version 0.4.0 or later (compatible with Vitest 1.x).
  • buildRestructure your test files to a separate `tests/` directory and adjust `include` to `['src/**/*.ts']` and `exclude` to `['tests/**/*.ts']`.
  • buildClear the coverage cache: `rm -rf node_modules/.cache/coverage` and rerun.
( 05 )How to verify

A fix you cannot prove is a guess. Close the loop.

  • verifiedRun `npx vitest --coverage` and confirm the terminal shows 'Coverage data for ...' for each source file.
  • verifiedOpen `coverage/index.html` in a browser — the file tree should list your source files with coverage percentages.
  • verifiedCheck `coverage/lcov.info` — it should contain entries for each source file with line coverage data.
  • verifiedRun `npx vitest --coverage --reporter=json` and inspect the JSON output for `coverageMap` property with source file paths.
  • verifiedIntentionally add an uncovered line in a source file and see if coverage drops — this confirms instrumentation is active.
  • verifiedRun the same command on CI (e.g., `npx vitest --coverage`) and compare the `coverage/` directory contents.
( 06 )Mistakes to avoid

Things that make this bug worse or harder to find.

  • warningSetting `coverage.all: false` (the default) and expecting all source files to be covered — they won't.
  • warningUsing `coverage.provider: 'v8'` without installing `@vitest/coverage-v8` — Vitest will silently use istanbul or fail.
  • warningForgetting to add `@vitest/coverage-c8` to `devDependencies` — it won't be installed automatically.
  • warningAssuming coverage works the same as Jest's `--coverage` — Vitest defaults differ.
  • warningPlacing coverage config in `test.coverage` instead of `coverage` — the correct key is `coverage` at the top level of the Vitest config.
  • warningRunning `vitest` without `--coverage` flag — coverage is not collected by default.
( 07 )War story

Empty Coverage Report After Migration from Jest to Vitest

Senior Frontend EngineerVitest 1.6.0, React 18, TypeScript 5.4, Vite 5.2, GitHub Actions

Timeline

  1. 09:00Merge PR to migrate from Jest to Vitest. Tests pass locally.
  2. 09:15CI pipeline runs tests and coverage step; coverage report shows 0% for all files.
  3. 09:30Check CI logs: 'No coverage data collected, did you run with --coverage?'.
  4. 09:45Locally run `npx vitest --coverage` — same error. Check package.json: `@vitest/coverage-c8` is present.
  5. 10:00Inspect vitest.config.ts: `coverage: { provider: 'c8', all: false, include: ['src/**/*.ts'] }`.
  6. 10:10Set `all: true` and rerun. Coverage now shows 100% for everything — but that's suspicious.
  7. 10:20Realize `include` pattern is correct but `all: false` means only files imported by tests are instrumented. Tests import only a few files.
  8. 10:30Set `all: true` and add `exclude: ['**/*.test.ts', '**/*.spec.ts']`. Rerun: correct coverage values.
  9. 10:45Push fix. CI passes with proper coverage report.

We migrated a large React project from Jest to Vitest for faster test runs. The migration went smoothly — all tests passed, both locally and in CI. But when the coverage step ran in the pipeline, the coverage report showed 0% for all source files. The terminal output said 'No coverage data collected, did you run with --coverage?' even though the script was `vitest --coverage`. I assumed it was a CI environment issue, so I ran the same command locally. Same error. That's when I knew the problem was in the config.

I checked package.json and saw `@vitest/coverage-c8` was listed in devDependencies. Then I looked at vitest.config.ts: coverage provider was 'c8', but `all: false` (the default). The `include` was `['src/**/*.ts']` which is correct, but with `all: false`, only files that are actually imported by test files are instrumented. In our project, many source files are not directly imported by tests (e.g., utility files used only in production). So the collector reported 0% because it didn't see those files at all.

I changed `all` to `true` and added an `exclude` pattern to ignore test files themselves. That fixed it. The coverage report now shows the real percentages. The lesson: Vitest's default `all: false` is different from Jest's default behavior (which includes all files by default). Always explicitly set `all: true` if you want full project coverage.

Root cause

Vitest's `coverage.all` defaults to `false`, so only files imported by tests are instrumented. Source files not imported are ignored, leading to empty or 0% coverage.

The fix

Set `coverage.all: true` in vitest.config.ts and add appropriate `exclude` patterns to ignore test files.

The lesson

Always verify coverage config after migrating from Jest. Vitest's coverage defaults differ — especially `all: false`. Read the docs carefully.

( 08 )How Vitest Coverage Collection Works

Vitest uses a coverage provider (c8 or Istanbul) to instrument JavaScript/TypeScript files. The provider wraps function calls, branches, and lines with counters. When tests run, these counters increment. After all tests, Vitest aggregates the counters and generates reports.

The key configuration is the `coverage` object in vitest.config.ts. The `provider` field selects the engine: `'c8'` (default, uses V8's built-in coverage) or `'istanbul'` (babel-based, more compatible). The `all` field controls whether to instrument all files matching `include` or only files that are imported. When `all: false`, only files that are actually imported in test files get instrumented. This is the most common pitfall: if your test files import only a small subset of source files, the rest will show 0% or not appear at all.

( 09 )The `all: true` Trap: Including Unused Files

Setting `all: true` tells Vitest to instrument every file matching `coverage.include` patterns, regardless of whether tests import them. This is usually what you want for accurate coverage. However, it can lead to unexpectedly low coverage if your include patterns are too broad (e.g., including vendor files or generated code).

To mitigate, always pair `all: true` with explicit `exclude` patterns. For example: `exclude: ['**/*.test.ts', '**/*.spec.ts', '**/node_modules/**', '**/dist/**']`. Also consider using `coverage.reporter` to generate multiple reports (html, lcov, text) for deeper insight.

( 10 )Provider-Specific Gotchas

c8 (V8 coverage) requires Node.js >=14 and works best with modern ES modules. If your project uses CommonJS or older Node, switch to Istanbul: set `provider: 'istanbul'` and install `@vitest/coverage-istanbul`. Istanbul also supports more customization like `watermarks` and `perFile` thresholds.

Another issue: c8 uses a native addon that can fail on certain systems (e.g., Alpine Linux in Docker). If you see segfaults or missing coverage on CI, try Istanbul. To check if c8 works, run `node -e "require('v8').takeCoverage()"` — if it throws, c8 won't work.

( 11 )Debugging with Verbose Output and JSON Reporter

When coverage fails silently, use the `--reporter=verbose` flag: `npx vitest --coverage --reporter=verbose`. This prints a list of every file being instrumented and its coverage stats. Look for missing files or 'no data' entries.

For programmatic debugging, use `--reporter=json` and inspect the `coverageMap` property. The JSON output includes a `coverageMap` object with file paths as keys. If your source file is missing from this map, it wasn't instrumented. Example command: `npx vitest --coverage --reporter=json > coverage.json && cat coverage.json | jq '.coverageMap'`.

( 12 )CI vs Local: Environment Differences

Coverage often works locally but fails on CI. Common reasons: different Node version (c8 needs >=14), missing environment variables, or different working directory. Ensure your CI runs `npm ci` to install exact dependencies, including coverage provider packages.

Another CI-specific issue: parallel test execution. If your tests run in multiple shards, coverage collection may be partial. Vitest supports `--coverage` with `--pool=forks` or `--pool=threads`; ensure the pool is consistent. For large projects, use `coverage.all: true` and include all source files to avoid shard-specific gaps.

Frequently asked questions

Why does coverage show 0% even though tests pass?

Most likely `coverage.all` is `false` (default), and your test files don't import the source files you expect. Set `all: true` in your Vitest config and ensure `include` covers your source directory.

What's the difference between `@vitest/coverage-c8` and `@vitest/coverage-istanbul`?

`@vitest/coverage-c8` uses V8's native coverage (faster, Node >=14 only). `@vitest/coverage-istanbul` uses Istanbul (babel-based, compatible with older Node and CommonJS). If you need advanced features like `thresholds` or `watermarks`, use Istanbul.

How do I include all source files in coverage, not just the ones tested?

Set `coverage.all: true` in `vitest.config.ts`. Then use `coverage.include` to specify which files to cover (e.g., `['src/**/*.ts']`) and `coverage.exclude` to exclude test files and other non-source files.

Coverage works locally but not on CI. What could be different?

Check Node version (c8 needs >=14), ensure `@vitest/coverage-c8` is installed via `npm ci`, and verify that the working directory matches. Also, CI may have different environment variables—try running with `NODE_ENV=test` to simulate.

I see 'No coverage data collected' even with `--coverage`. What now?

First, confirm the coverage provider is installed. Then check if your tests are actually running. Add `--reporter=verbose` to see what files are instrumented. If nothing appears, your config likely excludes all files. Start with `coverage: { all: true, provider: 'c8' }` and adjust.