Skip to content

Vitest — Coverage

OWNER: marije, judith ALSO_USED_BY: antje LAST_VERIFIED: 2026-03-26 GE_STACK_VERSION: vitest ^4.0.18, @vitest/coverage-v8 ^4.0.18


Overview

Coverage measures how much source code is exercised by tests. GE uses V8 provider for speed and accuracy. Thresholds are enforced in CI. Agents use this page to understand coverage configuration, thresholds, and meaningful coverage strategies.


V8 vs Istanbul

V8 Istanbul
Speed Fast (native V8 instrumentation) Slower (source code transformation)
Accuracy Very accurate for Node/Vite Battle-tested, well-understood
Setup Zero config with @vitest/coverage-v8 Requires @vitest/coverage-istanbul
GE default Yes No

CHECK: Coverage provider selection. THEN: Use V8 (@vitest/coverage-v8) — it is the GE standard. IF: V8 produces incorrect results for a specific edge case. THEN: Switch that project to Istanbul and document why.


GE Coverage Thresholds

// vitest.config.ts
coverage: {
  provider: "v8",
  include: ["src/**/*.{ts,tsx}"],
  exclude: [
    "src/**/*.d.ts",
    "src/**/index.ts",         // Re-export barrels
    "src/**/*.stories.{ts,tsx}", // Storybook stories
    "src/types/**",             // Type-only files
    "src/**/constants.ts",      // Static constants
  ],
  thresholds: {
    statements: 80,
    branches: 75,
    functions: 80,
    lines: 80,
  },
}
Metric Threshold What it measures
Statements 80% Individual statements executed
Branches 75% if/else/switch paths taken
Functions 80% Functions called at least once
Lines 80% Source lines executed

CHECK: Coverage thresholds are configured. THEN: Branches threshold is intentionally lower (75%) — some error paths are hard to reach. THEN: Never set any threshold below 70% — that signals missing tests. THEN: Never set thresholds at 100% — it incentivizes gaming over quality.


Meaningful Coverage (Not Gaming Metrics)

CHECK: Coverage is below threshold. IF: Tests exist but coverage is low. THEN: Identify untested branches and error paths — add targeted tests. IF: Coverage is at threshold but tests are shallow. THEN: Coverage is being gamed — fix the tests.

What Counts as Gaming

ANTI_PATTERN: Tests that call functions without asserting results.

// GAMING — this hits the line but proves nothing
it("runs the function", () => {
  calculateTotal(100, 0.1) // no expect()
})
FIX: Every test MUST have at least one meaningful assertion.

ANTI_PATTERN: Testing implementation details to inflate branch coverage.

// GAMING — tests internal branching, not behavior
it("enters the else branch", () => {
  // Contrived input just to hit a branch
})
FIX: Test observable behavior from the caller's perspective.

ANTI_PATTERN: Excluding files to meet thresholds. FIX: Only exclude files that genuinely cannot be unit tested (type files, re-export barrels, constants).

What Counts as Meaningful

// MEANINGFUL — tests behavior, verifies edge cases
describe("calculateShipping", () => {
  it("returns free shipping for orders over 50 EUR", () => {
    expect(calculateShipping(75)).toBe(0)
  })

  it("returns 4.95 for orders under 50 EUR", () => {
    expect(calculateShipping(30)).toBe(4.95)
  })

  it("returns 4.95 for exactly 50 EUR (boundary)", () => {
    expect(calculateShipping(50)).toBe(4.95)
  })

  it("throws for negative amounts", () => {
    expect(() => calculateShipping(-10)).toThrow("Invalid amount")
  })
})

Coverage in CI

# In GitHub Actions workflow
- name: Run tests with coverage
  run: npx vitest run --coverage

- name: Check coverage thresholds
  run: npx vitest run --coverage --coverage.thresholds.100
  # Fails CI if any threshold is not met

CHECK: CI pipeline includes tests. THEN: Coverage runs on every PR. THEN: Coverage report is generated as an artifact. THEN: Threshold failure blocks merge.


Vitest v4 Coverage Changes

CHECK: Migrating coverage config from Vitest v3 to v4. IF: Using coverage.all: true to include untested files in report. THEN: Removed in v4 — define coverage.include patterns explicitly instead. IF: Using line-level ignore comments (v8 ignore next). THEN: Works in v4.1+ for both V8 and Istanbul providers.

// v4.1+: Ignore specific lines from coverage
/* v8 ignore start */
if (process.env.NODE_ENV === "development") {
  enableDevTools()
}
/* v8 ignore stop */

Coverage for Changed Files Only

Vitest v4.1 introduces coverage.changed to limit coverage to modified files only. Useful for large codebases where full coverage runs are slow.

coverage: {
  changed: true, // Only measure coverage for files changed in this PR
}

CHECK: Coverage runs are slow in CI (> 2 minutes). IF: The project has 500+ source files. THEN: Consider enabling coverage.changed for PR checks. THEN: Keep full coverage runs on main branch merges.


Viewing Coverage Reports

# Generate HTML report
npx vitest run --coverage --reporter=html

# Opens in browser
open coverage/index.html

CHECK: Coverage results need human review. THEN: Generate HTML report — it shows line-by-line highlighting. THEN: Focus review on red (uncovered) lines in business-critical files.


Cross-References

READ_ALSO: wiki/docs/stack/vitest/index.md READ_ALSO: wiki/docs/stack/vitest/patterns.md READ_ALSO: wiki/docs/stack/vitest/pitfalls.md READ_ALSO: wiki/docs/stack/vitest/checklist.md