Code coverage
Use Bun's built-in code coverage reporting to track test coverage and find untested code
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:
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:
[test]
# Always enable coverage
coverage = trueBy default, coverage reports exclude test files and use sourcemaps. Both are configurable in bunfig.toml.
[test]
coverageSkipTestFiles = false # default trueCoverage 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#
[test]
# To require 90% line-level and function-level coverage
coverageThreshold = 0.9Detailed Thresholds#
[test]
# To set different thresholds for lines and functions
coverageThreshold = { lines = 0.9, functions = 0.9, statements = 0.9 }Setting any of these thresholds causes 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.
[test]
coverageReporter = ["text", "lcov"] # default ["text"]
coverageDir = "path/to/somewhere" # default "coverage"Available Reporters#
| Reporter | Description |
|---|---|
text | Prints a text summary of the coverage to the console |
lcov | Save coverage in lcov format |
LCOV Coverage Reporter#
The lcov reporter writes an lcov.info file to the coverage directory.
[test]
coverageReporter = "lcov"# Or via CLI
bun test --coverage --coverage-reporter=lcovTools 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#
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.infoExcluding Files from Coverage#
Skip Test Files#
Coverage reports exclude test files by default. To include them:
[test]
coverageSkipTestFiles = false # default trueWhen coverageSkipTestFiles is true (the default), the coverage report excludes files matching test patterns (for example *.test.ts, *.spec.js).
Ignore Specific Paths and Patterns#
coveragePathIgnorePatterns excludes specific files or file patterns from coverage reports:
[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. Bun excludes files matching any of the patterns from coverage calculation and reporting in both text and LCOV output.
Common Use Cases#
[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 make coverage reports ignore this source map, set test.coverageIgnoreSourcemaps to true. You rarely want this outside of advanced use cases.
[test]
coverageIgnoreSourcemaps = true # default falseWhen 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_modulesdirectories - Exclude files loaded with non-JS/TS loaders (for example
.css,.txt) unless you specify a custom JS loader - Exclude test files themselves (include them with
coverageSkipTestFiles = false) - Can exclude additional files with
coveragePathIgnorePatterns
Advanced Configuration#
Custom Coverage Directory#
[test]
coverageDir = "coverage-reports" # default "coverage"Multiple Reporters#
[test]
coverageReporter = ["text", "lcov"]Coverage with Specific Test Patterns#
# 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#
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: trueGitLab CI Example#
test:coverage:
stage: test
script:
- bun install
- bun test --coverage --coverage-reporter=text --coverage-reporter=lcov
coverage: '/All files\s*\|\s*[\d.]+\s*\|\s*(\d+\.\d+)/'
artifacts:
paths:
- coverage/lcov.infoInterpreting 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 can be tested completely
- Lower coverage for UI components: Often acceptable as they may require integration tests
Best Practices#
Focus on Quality, Not Just Quantity#
// 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("user input validation", () => {
// Test normal case
expect(validateEmail("[email protected]")).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#
# Run coverage to identify untested code
bun test --coverage
# Look at specific files that need attention
bun test --coverage src/critical-module.tsCombine with Other Quality Metrics#
Coverage is only 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.
// 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:
- Check if source maps are working correctly
- Verify file patterns in
coveragePathIgnorePatterns - Ensure test files import the code to test
Performance Issues with Large Codebases#
For large projects, coverage collection can slow down tests:
[test]
# Exclude large directories you don't need coverage for
coveragePathIgnorePatterns = [
"vendor/**",
"generated/**"
]Consider running coverage only on CI or specific branches rather than every test run during development.