DEV Community

Leonardo Marciano
Leonardo Marciano

Posted on

Integrating Code Coverage with Codecov in Encore.ts

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:

  1. Measure – Codecov analyzes your test suite execution.
  2. Report – Coverage data appears in PRs and dashboards.
  3. Improve – Team focuses on untested areas.
  4. 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
Enter fullscreen mode Exit fullscreen mode

Create a new project

encore app create codecov-demo && cd codecov-demo
npm install --save-dev vitest @vitest/coverage-v8
Enter fullscreen mode Exit fullscreen mode

2. Create a Codecov Account

  1. Go to codecov.io and sign up with GitHub.
  2. Add your repository (it will appear after your first push).
  3. Copy your repository upload token from the settings page using Github Actions

Codecov repository settings page showing the upload token section

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"
  }
}
Enter fullscreen mode Exit fullscreen mode

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 test script is perfect for CI/CD pipelines, while test:coverage leverages 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/**'],
    },
  },
});
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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 our codecov.yml configuration, which only tracks files within this directory. You can adjust the paths in codecov.yml if you prefer a different structure like services/.

1. Create a Math Service

apps/math/encore.service.ts

import { Service } from "encore.dev/service";

export default new Service("math");
Enter fullscreen mode Exit fullscreen mode

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;
  }
}
Enter fullscreen mode Exit fullscreen mode

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 };
  }
);
Enter fullscreen mode Exit fullscreen mode

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
});
Enter fullscreen mode Exit fullscreen mode

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 }}
Enter fullscreen mode Exit fullscreen mode

Setting Up GitHub Secrets

1. Get Your Encore Auth Key

  1. Open your Encore dashboard at app.encore.dev.
  2. Navigate to Settings → Auth Tokens.
  3. Click Create New Token and name it "GitHub Actions".
  4. Copy the generated token.

Encore dashboard showing the Auth Tokens page with Create New Token button

2. Get Your Codecov Token

  1. Go to your repository settings in Codecov.
  2. Find the Upload Token section.
  3. Copy the repository token.

Codecov repository settings showing the upload token

3. Add Secrets to GitHub

  1. Go to your GitHub repository.
  2. Navigate to Settings → Secrets and variables → Actions.
  3. Click New repository secret.
  4. Add two secrets:
    • Name: ENCORE_AUTH, Value: Your Encore auth token
    • Name: CODECOV_TOKEN, Value: Your Codecov upload token

GitHub repository settings showing the secrets configuration page with both secrets added


Running the Application

  1. Run tests locally
   npm run test:coverage
Enter fullscreen mode Exit fullscreen mode

You can also use the standard test script for CI-style testing:

   npm run test
Enter fullscreen mode Exit fullscreen mode

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            
---------------|---------|----------|---------|---------|-------------------
Enter fullscreen mode Exit fullscreen mode
  1. Push to GitHub
   git add .
   git commit -m "Add calculator with tests"
   git push origin main
Enter fullscreen mode Exit fullscreen mode
  1. Check GitHub Actions

Go to the Actions tab in your repository to see the workflow running.

GitHub Actions page showing the codecov workflow in progress

  1. View Coverage Report

Once complete, check Codecov for your coverage report.

Codecov dashboard showing the coverage report with file tree and coverage percentages


Adding Coverage Badge

Make your coverage visible in your README:

README.md

# My Encore App

[![codecov](https://codecov.io/gh/YOUR_USERNAME/YOUR_REPO/branch/main/graph/badge.svg)](https://codecov.io/gh/YOUR_USERNAME/YOUR_REPO)

This is my Encore backend with automated test coverage tracking.
Enter fullscreen mode Exit fullscreen mode

GitHub repository README showing the codecov badge displaying coverage percentage


Deployment

1. Push!

git remote add origin https://your-repo-url.com/repo
git push encore main
Enter fullscreen mode Exit fullscreen mode

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

GitHub pull request showing Codecov comment with coverage diff

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
Enter fullscreen mode Exit fullscreen mode

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)