Skip to main content
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

bun:test can report which lines of code your tests cover. Pass --coverage to print a coverage report to the console:
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

To enable coverage reporting by default, add this to your bunfig.toml:
bunfig.toml
[test]
# Always enable coverage
coverage = true
By default, coverage reports include test files and exclude sourcemaps. Both are configurable in bunfig.toml.
bunfig.toml
[test]
coverageSkipTestFiles = true  # default false

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

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

Detailed Thresholds

bunfig.toml
[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

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.
bunfig.toml
[test]
coverageReporter = ["text", "lcov"]  # default ["text"]
coverageDir = "path/to/somewhere"    # default "coverage"

Available Reporters

ReporterDescription
textPrints a text summary of the coverage to the console
lcovSave coverage in lcov format

LCOV Coverage Reporter

The lcov reporter writes an lcov.info file to the coverage directory.
bunfig.toml
[test]
coverageReporter = "lcov"
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

.github/workflows/test.yml
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

Skip Test Files

Coverage reports include test files by default. Exclude them with:
bunfig.toml
[test]
coverageSkipTestFiles = true  # default false
This excludes files matching test patterns (for example *.test.ts, *.spec.js) from the coverage report.

Ignore Specific Paths and Patterns

coveragePathIgnorePatterns excludes specific files or file patterns from coverage reports:
bunfig.toml
[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

bunfig.toml
[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

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.
bunfig.toml
[test]
coverageIgnoreSourcemaps = true  # default false
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.

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
  • Include test files themselves (can be disabled with coverageSkipTestFiles = true)
  • Can exclude additional files with coveragePathIgnorePatterns

Advanced Configuration

Custom Coverage Directory

bunfig.toml
[test]
coverageDir = "coverage-reports"  # default "coverage"

Multiple Reporters

bunfig.toml
[test]
coverageReporter = ["text", "lcov"]

Coverage with Specific Test Patterns

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

GitHub Actions Example

.github/workflows/coverage.yml
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.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

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

  • 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

Focus on Quality, Not Just Quantity

https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35btest.ts
// 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

https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35btest.ts
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

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

Coverage is just one metric. Also consider:
  • Code review quality
  • Integration test coverage
  • Error handling tests
  • Performance tests
  • Type safety

Troubleshooting

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.
https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35btest.ts
// 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

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

For large projects, coverage collection can slow down tests:
bunfig.toml
[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.