# Code coverage (/docs/test/code-coverage)

<!-- agent-signals: reading_time_min: 7 · est_tokens: 3029 · updated: 2026-07-28 -->
Related: [Writing tests](/docs/test/writing-tests.md), [Test configuration](/docs/test/configuration.md), [Runtime behavior](/docs/test/runtime-behavior.md), [Finding tests](/docs/test/discovery.md), [Lifecycle hooks](/docs/test/lifecycle.md), [Mocks](/docs/test/mocks.md)

Bun's test runner has built-in code coverage reporting. Use it to see how much of your codebase your tests cover and to find untested code.

## Enabling Coverage [#enabling-coverage]

`bun:test` can report which lines of code your tests cover. Pass `--coverage` to print a coverage report to the console:

```bash terminal icon="terminal"
bun test --coverage

-------------|---------|---------|-------------------
File         | % Funcs | % Lines | Uncovered Line #s
-------------|---------|---------|-------------------
All files    |   38.89 |   42.11 |
 index-0.ts  |   33.33 |   36.84 | 10-15,19-24
 index-1.ts  |   33.33 |   36.84 | 10-15,19-24
 index-10.ts |   33.33 |   36.84 | 10-15,19-24
 index-2.ts  |   33.33 |   36.84 | 10-15,19-24
 index-3.ts  |   33.33 |   36.84 | 10-15,19-24
 index-4.ts  |   33.33 |   36.84 | 10-15,19-24
 index-5.ts  |   33.33 |   36.84 | 10-15,19-24
 index-6.ts  |   33.33 |   36.84 | 10-15,19-24
 index-7.ts  |   33.33 |   36.84 | 10-15,19-24
 index-8.ts  |   33.33 |   36.84 | 10-15,19-24
 index-9.ts  |   33.33 |   36.84 | 10-15,19-24
 index.ts    |  100.00 |  100.00 |
-------------|---------|---------|-------------------
```

### Enable by Default [#enable-by-default]

To enable coverage reporting by default, add this to your `bunfig.toml`:

```toml title="bunfig.toml"
[test]
# Always enable coverage
coverage = true
```

By default, coverage reports exclude test files and use sourcemaps. Both are configurable in `bunfig.toml`.

```toml title="bunfig.toml" icon="settings"
[test]
coverageSkipTestFiles = false  # default true
```

## Coverage Thresholds [#coverage-thresholds]

Set a coverage threshold in `bunfig.toml`. If your test suite does not meet or exceed it, `bun test` exits with a non-zero exit code.

### Simple Threshold [#simple-threshold]

```toml title="bunfig.toml" icon="settings"
[test]
# To require 90% line-level and function-level coverage
coverageThreshold = 0.9
```

### Detailed Thresholds [#detailed-thresholds]

```toml title="bunfig.toml" icon="settings"
[test]
# To set different thresholds for lines and functions
coverageThreshold = { lines = 0.9, functions = 0.9, statements = 0.9 }
```

Setting any of these thresholds enables `fail_on_low_coverage`, causing the test run to fail if coverage is below the threshold.

## Coverage Reporters [#coverage-reporters]

By default, Bun prints coverage reports to the console.

To save a report for CI or other tools, pass `--coverage-reporter=lcov` on the command line or set `coverageReporter` in `bunfig.toml`.

```toml title="bunfig.toml" icon="settings"
[test]
coverageReporter = ["text", "lcov"]  # default ["text"]
coverageDir = "path/to/somewhere"    # default "coverage"
```

### Available Reporters [#available-reporters]

| Reporter | Description                                          |
| -------- | ---------------------------------------------------- |
| `text`   | Prints a text summary of the coverage to the console |
| `lcov`   | Save coverage in lcov format                         |

### LCOV Coverage Reporter [#lcov-coverage-reporter]

The lcov reporter writes an `lcov.info` file to the coverage directory.

```toml title="bunfig.toml" icon="settings"
[test]
coverageReporter = "lcov"
```

```bash terminal icon="terminal"
# Or via CLI
bun test --coverage --coverage-reporter=lcov
```

Tools and services that read the LCOV format include:

* **Code editors**: VS Code extensions can show coverage inline
* **CI/CD services**: GitHub Actions, GitLab CI, CircleCI
* **Coverage services**: Codecov, Coveralls
* **IDEs**: WebStorm, IntelliJ IDEA

#### Using LCOV with GitHub Actions [#using-lcov-with-github-actions]

```yaml title=".github/workflows/test.yml" icon="file-code"
name: Test with Coverage
on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: oven-sh/setup-bun@v2
      - run: bun install
      - run: bun test --coverage --coverage-reporter=lcov
      - name: Upload coverage to Codecov
        uses: codecov/codecov-action@v3
        with:
          file: ./coverage/lcov.info
```

## Excluding Files from Coverage [#excluding-files-from-coverage]

### Skip Test Files [#skip-test-files]

Coverage reports exclude test files by default. To include them:

```toml title="bunfig.toml" icon="settings"
[test]
coverageSkipTestFiles = false  # default true
```

When `coverageSkipTestFiles` is `true` (the default), files matching test patterns (for example `*.test.ts`, `*.spec.js`) are excluded from the coverage report.

### Ignore Specific Paths and Patterns [#ignore-specific-paths-and-patterns]

`coveragePathIgnorePatterns` excludes specific files or file patterns from coverage reports:

```toml title="bunfig.toml" icon="settings"
[test]
# Single pattern
coveragePathIgnorePatterns = "**/*.spec.ts"

# Multiple patterns
coveragePathIgnorePatterns = [
  "**/*.spec.ts",
  "**/*.test.ts",
  "src/utils/**",
  "*.config.js"
]
```

The option accepts glob patterns and works like Jest's `collectCoverageFrom` ignore patterns. Files matching any of the patterns are excluded from coverage calculation and reporting in both text and LCOV output.

#### Common Use Cases [#common-use-cases]

```toml title="bunfig.toml" icon="settings"
[test]
coveragePathIgnorePatterns = [
  # Exclude utility files
  "src/utils/**",

  # Exclude configuration files
  "*.config.js",
  "webpack.config.ts",
  "vite.config.ts",

  # Exclude specific test patterns
  "**/*.spec.ts",
  "**/*.e2e.ts",

  # Exclude build artifacts
  "dist/**",
  "build/**",

  # Exclude generated files
  "src/generated/**",
  "**/*.generated.ts",

  # Exclude vendor/third-party code
  "vendor/**",
  "third-party/**"
]
```

## Sourcemaps [#sourcemaps]

Bun transpiles all files by default, generating an internal source map that maps lines of your original source code onto Bun's internal representation. To disable this, set `test.coverageIgnoreSourcemaps` to `true`; you rarely want this outside of advanced use cases.

```toml title="bunfig.toml" icon="settings"
[test]
coverageIgnoreSourcemaps = true  # default false
```

<Warning>
  When using this option, you probably want to stick a `// @bun` comment at the top of the source file to opt out of the
  transpilation process.
</Warning>

## Coverage Defaults [#coverage-defaults]

By default, coverage reports:

* **Exclude** `node_modules` directories
* **Exclude** files loaded with non-JS/TS loaders (for example `.css`, `.txt`) unless a custom JS loader is specified
* **Exclude** test files themselves (can be included with `coverageSkipTestFiles = false`)
* Can exclude additional files with `coveragePathIgnorePatterns`

## Advanced Configuration [#advanced-configuration]

### Custom Coverage Directory [#custom-coverage-directory]

```toml title="bunfig.toml" icon="settings"
[test]
coverageDir = "coverage-reports"  # default "coverage"
```

### Multiple Reporters [#multiple-reporters]

```toml title="bunfig.toml" icon="settings"
[test]
coverageReporter = ["text", "lcov"]
```

### Coverage with Specific Test Patterns [#coverage-with-specific-test-patterns]

```bash terminal icon="terminal"
# Run coverage only on specific test files
bun test --coverage src/components/*.test.ts

# Run coverage with name pattern
bun test --coverage --test-name-pattern="API"
```

## CI/CD Integration [#cicd-integration]

### GitHub Actions Example [#github-actions-example]

```yaml title=".github/workflows/coverage.yml" icon="file-code"
name: Coverage Report
on: [push, pull_request]

jobs:
  coverage:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Setup Bun
        uses: oven-sh/setup-bun@v2

      - name: Install dependencies
        run: bun install

      - name: Run tests with coverage
        run: bun test --coverage --coverage-reporter=lcov

      - name: Upload to Codecov
        uses: codecov/codecov-action@v3
        with:
          file: ./coverage/lcov.info
          fail_ci_if_error: true
```

### GitLab CI Example [#gitlab-ci-example]

```yaml title=".gitlab-ci.yml"
test:coverage:
  stage: test
  script:
    - bun install
    - bun test --coverage --coverage-reporter=lcov
  coverage: '/Lines\s*:\s*(\d+.\d+)%/'
  artifacts:
    reports:
      coverage_report:
        coverage_format: cobertura
        path: coverage/lcov.info
```

## Interpreting Coverage Reports [#interpreting-coverage-reports]

### Text Output Explanation [#text-output-explanation]

```
-------------|---------|---------|-------------------
File         | % Funcs | % Lines | Uncovered Line #s
-------------|---------|---------|-------------------
All files    |   85.71 |   90.48 |
 src/        |   85.71 |   90.48 |
  utils.ts   |  100.00 |  100.00 |
  api.ts     |   75.00 |   85.71 | 15-18,25
  main.ts    |   80.00 |   88.89 | 42,50-52
-------------|---------|---------|-------------------
```

* **% Funcs**: Percentage of functions called during tests
* **% Lines**: Percentage of executable lines run during tests
* **Uncovered Line #s**: Line numbers that were never executed

### What to Aim For [#what-to-aim-for]

* **80%+ overall coverage**: Generally considered good
* **90%+ critical paths**: Important business logic should be well-tested
* **100% utility functions**: Pure functions and utilities are easy to test completely
* **Lower coverage for UI components**: Often acceptable as they may require integration tests

## Best Practices [#best-practices]

### Focus on Quality, Not Just Quantity [#focus-on-quality-not-just-quantity]

```ts title="test.ts" icon="/icons/typescript.svg"
// Good: Test actual functionality
test("calculateTax should handle different tax rates", () => {
  expect(calculateTax(100, 0.08)).toBe(8);
  expect(calculateTax(100, 0.1)).toBe(10);
  expect(calculateTax(0, 0.08)).toBe(0);
});

// Avoid: Just hitting lines for coverage
test("calculateTax exists", () => {
  calculateTax(100, 0.08); // No assertions!
});
```

### Test Edge Cases [#test-edge-cases]

```ts title="test.ts" icon="/icons/typescript.svg"
test("user input validation", () => {
  // Test normal case
  expect(validateEmail("user@example.com")).toBe(true);

  // Test edge cases that improve coverage meaningfully
  expect(validateEmail("")).toBe(false);
  expect(validateEmail("invalid")).toBe(false);
  expect(validateEmail(null)).toBe(false);
});
```

### Use Coverage to Find Missing Tests [#use-coverage-to-find-missing-tests]

```bash terminal icon="terminal"
# Run coverage to identify untested code
bun test --coverage

# Look at specific files that need attention
bun test --coverage src/critical-module.ts
```

### Combine with Other Quality Metrics [#combine-with-other-quality-metrics]

Coverage is just one metric. Also consider:

* **Code review quality**
* **Integration test coverage**
* **Error handling tests**
* **Performance tests**
* **Type safety**

## Troubleshooting [#troubleshooting]

### Coverage Not Showing for Some Files [#coverage-not-showing-for-some-files]

If files aren't appearing in coverage reports, your tests might not import them. Coverage only tracks files that are loaded.

```ts title="test.ts" icon="/icons/typescript.svg"
// Make sure to import the modules you want to test
import { myFunction } from "../src/my-module";

test("my function works", () => {
  expect(myFunction()).toBeDefined();
});
```

### False Coverage Reports [#false-coverage-reports]

If you see coverage reports that don't match your expectations:

1. Check if source maps are working correctly
2. Verify file patterns in `coveragePathIgnorePatterns`
3. Ensure test files are actually importing the code to test

### Performance Issues with Large Codebases [#performance-issues-with-large-codebases]

For large projects, coverage collection can slow down tests:

```toml title="bunfig.toml" icon="settings"
[test]
# Exclude large directories you don't need coverage for
coveragePathIgnorePatterns = [
  "node_modules/**",
  "vendor/**",
  "generated/**"
]
```

Consider running coverage only on CI or specific branches rather than every test run during development.
