Track your test coverage and maintain code quality across deployments.
Introduction
In this tutorial, we'll integrate Codecov with your Encore.ts backend to automatically track test coverage on every push. The final setup gives you:
- Coverage reports – Visual dashboards showing which lines are tested and which aren't.
- Pull request comments – Automated coverage diffs showing impact of new changes.
- GitHub badges – Display your coverage percentage directly in your README.
This is essential for teams that want to maintain high code quality standards and catch untested code before it reaches production.
Why Code Coverage Matters
Without coverage tracking, you're blind to:
- Untested edge cases – Critical error paths that only fail in production.
- Coverage regression – New features that reduce overall test coverage.
- Team accountability – No visibility into who's writing tests and who isn't.
Coverage tracking creates a quality feedback loop:
- Measure – Codecov analyzes your test suite execution.
- Report – Coverage data appears in PRs and dashboards.
- Improve – Team focuses on untested areas.
- Maintain – Coverage gates prevent regression.
A well-monitored codebase stays healthy as it grows.
Getting Started
1. Install Encore & Create Your App
Install Encore CLI
# macOS
brew install encoredev/tap/encore
# Windows (PowerShell)
iwr https://encore.dev/install.ps1 | iex
# Linux
curl -L https://encore.dev/install.sh | bash
Create a new project
encore app create codecov-demo && cd codecov-demo
npm install --save-dev vitest @vitest/coverage-v8
2. Create a Codecov Account
- Go to codecov.io and sign up with GitHub.
- Add your repository (it will appear after your first push).
- Copy your repository upload token from the settings page using Github Actions
3. Configure Your Test Setup
Update package.json
Add the test scripts to your package.json to enable coverage tracking:
{
"scripts": {
"test": "vitest run --coverage",
"test:coverage": "encore test"
}
}
Script explanations:
-
test- Runs Vitest with coverage reporting (for local development and CI) -
test:coverage- Uses Encore's built-in test runner which handles the Encore environment properly
Why two scripts? The
testscript is perfect for CI/CD pipelines, whiletest:coverageleverages Encore's test runner which automatically handles service dependencies and environment setup.
Create vitest.config.ts
import path from 'node:path';
/// <reference types="vitest" />
import { defineConfig } from 'vite';
export default defineConfig({
resolve: {
alias: {
'~encore': path.resolve(__dirname, './encore.gen'),
},
},
test: {
coverage: {
exclude: ['encore.gen/**', 'node_modules/**', '**/*.service.ts', '**/encore.service.ts'],
include: ['apps/**'],
},
},
});
Update .gitignore
Add coverage files to your .gitignore to avoid committing generated coverage reports:
.encore
encore.gen.go
encore.gen.cue
/.encore
node_modules
/encore.gen
# Coverage reports
coverage
.DS_Store
Important: Coverage reports are generated locally and in CI, but should never be committed to your repository. Codecov will handle the coverage data through its API.
Backend Implementation
Let's create a simple service with tests to demonstrate coverage tracking.
Note: We're using the
apps/directory structure to match ourcodecov.ymlconfiguration, which only tracks files within this directory. You can adjust the paths incodecov.ymlif you prefer a different structure likeservices/.
1. Create a Math Service
apps/math/encore.service.ts
import { Service } from "encore.dev/service";
export default new Service("math");
2. Add Business Logic
apps/math/calculator.ts
/**
* Calculator module with basic math operations
* This demonstrates code that needs test coverage
*/
export class Calculator {
/**
* Add two numbers together
*/
add(a: number, b: number): number {
return a + b;
}
/**
* Subtract b from a
*/
subtract(a: number, b: number): number {
return a - b;
}
/**
* Multiply two numbers
*/
multiply(a: number, b: number): number {
return a * b;
}
/**
* Divide a by b with error handling
*/
divide(a: number, b: number): number {
if (b === 0) {
throw new Error("Division by zero is not allowed");
}
return a / b;
}
/**
* Calculate percentage
*/
percentage(value: number, total: number): number {
if (total === 0) {
return 0;
}
return (value / total) * 100;
}
}
3. Create API Endpoints
apps/math/api.ts
import { api } from "encore.dev/api";
import { Calculator } from "./calculator";
const calc = new Calculator();
interface MathOperation {
a: number;
b: number;
}
interface MathResult {
result: number;
}
/**
* Add two numbers
*/
export const add = api(
{ method: "POST", path: "/math/add", expose: true },
async ({ a, b }: MathOperation): Promise<MathResult> => {
const result = calc.add(a, b);
return { result };
}
);
/**
* Subtract two numbers
*/
export const subtract = api(
{ method: "POST", path: "/math/subtract", expose: true },
async ({ a, b }: MathOperation): Promise<MathResult> => {
const result = calc.subtract(a, b);
return { result };
}
);
/**
* Multiply two numbers
*/
export const multiply = api(
{ method: "POST", path: "/math/multiply", expose: true },
async ({ a, b }: MathOperation): Promise<MathResult> => {
const result = calc.multiply(a, b);
return { result };
}
);
/**
* Divide two numbers
*/
export const divide = api(
{ method: "POST", path: "/math/divide", expose: true },
async ({ a, b }: MathOperation): Promise<MathResult> => {
const result = calc.divide(a, b);
return { result };
}
);
4. Write Unit Tests
apps/math/calculator.test.ts
import { describe, it, expect } from 'vitest';
import { Calculator } from './calculator';
describe('Calculator', () => {
const calc = new Calculator();
describe('add', () => {
it('should add two positive numbers', () => {
expect(calc.add(2, 3)).toBe(5);
});
it('should handle negative numbers', () => {
expect(calc.add(-1, -1)).toBe(-2);
});
});
describe('subtract', () => {
it('should subtract two numbers', () => {
expect(calc.subtract(5, 3)).toBe(2);
});
});
describe('multiply', () => {
it('should multiply two numbers', () => {
expect(calc.multiply(4, 5)).toBe(20);
});
});
describe('divide', () => {
it('should divide two numbers', () => {
expect(calc.divide(10, 2)).toBe(5);
});
it('should throw error for division by zero', () => {
expect(() => calc.divide(10, 0)).toThrow('Division by zero is not allowed');
});
});
// Note: We're intentionally not testing percentage() to demonstrate uncovered code
});
5. Configure GitHub Actions
Create .github/workflows/codecov.yml:
name: Run tests and upload coverage
on:
push:
jobs:
test:
name: Run tests and collect coverage
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 2
- name: Set up Node
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Set up Docker
uses: docker/setup-buildx-action@v3
- name: Install dependencies
run: npm install
- name: Install Encore CLI
run: |
curl -L https://encore.dev/install.sh | bash
echo "${HOME}/.encore/bin" >> $GITHUB_PATH
- name: Authenticate with Encore
run: encore auth login --auth-key=${{ secrets.ENCORE_AUTH }}
env:
ENCORE_AUTH: ${{ secrets.ENCORE_AUTH }}
- name: Run tests
run: npm run test:coverage
- name: Upload results to Codecov
uses: codecov/codecov-action@v5
with:
token: ${{ secrets.CODECOV_TOKEN }}
directory: ./coverage/
flags: typescript,backend
fail_ci_if_error: false
verbose: true
name: encore-example-coverage
working-directory: ${{ github.workspace }}
root_dir: ${{ github.workspace }}
Setting Up GitHub Secrets
1. Get Your Encore Auth Key
- Open your Encore dashboard at app.encore.dev.
- Navigate to Settings → Auth Tokens.
- Click Create New Token and name it "GitHub Actions".
- Copy the generated token.
2. Get Your Codecov Token
- Go to your repository settings in Codecov.
- Find the Upload Token section.
- Copy the repository token.
3. Add Secrets to GitHub
- Go to your GitHub repository.
- Navigate to Settings → Secrets and variables → Actions.
- Click New repository secret.
- Add two secrets:
-
Name:
ENCORE_AUTH, Value: Your Encore auth token -
Name:
CODECOV_TOKEN, Value: Your Codecov upload token
-
Name:
Running the Application
- Run tests locally
npm run test:coverage
You can also use the standard test script for CI-style testing:
npm run test
You'll see output like:
✓ apps/math/calculator.test.ts (6 tests) 6ms
✓ Calculator > add > should add two positive numbers 2ms
✓ Calculator > add > should handle negative numbers 0ms
✓ Calculator > subtract > should subtract two numbers 0ms
✓ Calculator > multiply > should multiply two numbers 0ms
✓ Calculator > divide > should divide two numbers 0ms
✓ Calculator > divide > should throw error for division by zero 1ms
Test Files 1 passed (1)
Tests 6 passed (6)
Start at 15:11:50
Duration 524ms (transform 57ms, setup 0ms, collect 40ms, tests 6ms, environment 0ms, prepare 202ms)
% Coverage report from v8
---------------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
---------------|---------|----------|---------|---------|-------------------
All files | 33.33 | 83.33 | 66.66 | 33.33 |
api.ts | 0 | 0 | 0 | 0 | 1-57
calculator.ts | 78.26 | 100 | 80 | 78.26 | 42-46
---------------|---------|----------|---------|---------|-------------------
- Push to GitHub
git add .
git commit -m "Add calculator with tests"
git push origin main
- Check GitHub Actions
Go to the Actions tab in your repository to see the workflow running.
- View Coverage Report
Once complete, check Codecov for your coverage report.
Adding Coverage Badge
Make your coverage visible in your README:
README.md
# My Encore App
[](https://codecov.io/gh/YOUR_USERNAME/YOUR_REPO)
This is my Encore backend with automated test coverage tracking.
Deployment
1. Push!
git remote add origin https://your-repo-url.com/repo
git push encore main
2. Coverage in Pull Requests
When you create pull requests, Codecov will automatically:
- Comment with coverage changes
- Show which lines are covered/uncovered
- Fail checks if coverage drops below threshold
3. Set Coverage Requirements (Optional)
Create codecov.yml in your repository root:
codecov:
require_ci_to_pass: false # Set to false during troubleshooting
notify:
wait_for_ci: true
branch: main
coverage:
precision: 2
round: down
range: "70...100"
status:
project:
default: # Overall project coverage
target: 100%
threshold: 1%
informational: false
if_ci_failed: error # fail the status if CI fails
only_pulls: false # post status on commits and PRs
paths:
- "apps/" # only include apps directory
flag_coverage_not_uploaded_behavior: include
# You can add additional project status checks for specific components
# frontend:
# flags:
# - frontend
# target: 90%
backend:
flags:
- backend
target: 95%
patch: # Coverage of the PR diff
default:
target: 100% # require 100% coverage for new/modified code
threshold: 0% # no threshold allowed for patch
base: auto # compare against parent commit
informational: false # block PRs if patch coverage is insufficient
only_pulls: true # only check patch coverage on PRs
# Report changes in coverage not directly modified in the PR
changes: true # enable unexpected coverage change detection
# Define flags for TypeScript codebase
flags:
typescript:
paths:
- apps/.*\.ts$
- apps/.*\.tsx$
carryforward: true
# Only include TypeScript files within the apps directory
ignore:
- "encore.gen/" # Ignore generated code
comment:
# Ensure comments are posted on PRs
layout: "header, diff, flags, files, footer"
behavior: new # Delete old comment and post new one
require_changes: false # Post comment even if there are no coverage changes
require_base: false # Don't require base report to post comment
require_head: true # Require head report to post comment
hide_project_coverage: false # Show both project and patch coverage
Understanding the Configuration
This comprehensive setup provides:
Coverage Standards:
- Project target: 100% overall coverage with 1% threshold
- Backend target: 95% for backend-specific code
- Patch target: 100% for all new/modified code
- Range: Visual indicators from 70% (red) to 100% (green)
Smart Filtering:
- Only tracks TypeScript files in the
apps/directory - Ignores Encore's generated code (
encore.gen/) - Separate flags for backend tracking
PR Integration:
- Comments on every PR with coverage details
- Shows file-by-file breakdown
- Blocks PRs if new code isn't 100% covered
- Detects unexpected coverage changes
Error Handling:
- Continues even if CI has issues (for troubleshooting)
- Fails status checks if CI completely fails
- Includes coverage even if flags aren't uploaded
Next Steps
-
Integration tests – Test your API endpoints with
supertest - E2E tests – Add Playwright for full user flow testing
- Branch coverage – Ensure all conditional paths are tested
- Coverage gates – Block merges that reduce coverage
- Team dashboards – Create coverage leaderboards
- Historical tracking – Monitor coverage trends over time
Congratulations!
You now have automated code coverage tracking that helps maintain quality as your Encore backend grows. Every push is analyzed, every PR shows impact, and your team stays accountable for test coverage.








Top comments (0)