Karma is the test runner that launches a browser, serves the compiled Angular test bundle, and reports results. For CI pipelines, ChromeHeadless is essential — it runs Chrome without a display server, making it compatible with Linux CI agents that have no GUI. Code coverage thresholds enforce minimum coverage targets and fail the build if they drop below the configured percentage — preventing coverage regression as new code is added.
Karma and Coverage Configuration
// ── karma.conf.js ─────────────────────────────────────────────────────────
module.exports = function(config) {
config.set({
basePath: '',
frameworks: ['jasmine', '@angular-devkit/build-angular'],
plugins: [
require('karma-jasmine'),
require('karma-chrome-launcher'),
require('karma-jasmine-html-reporter'),
require('karma-coverage'),
require('@angular-devkit/build-angular/plugins/karma'),
],
client: {
jasmine: {
random: true, // randomise test order to catch order dependencies
seed: '12345', // fixed seed for reproducibility in CI
timeoutInterval: 5000,
},
clearContext: false,
},
coverageReporter: {
dir: require('path').join(__dirname, './coverage'),
subdir: '.',
reporters: [
{ type: 'html' }, // browser-viewable HTML report
{ type: 'lcovonly' }, // for SonarQube / Codecov integration
{ type: 'text-summary' }, // console summary
],
},
reporters: ['progress', 'kjhtml', 'coverage'],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
browsers: ['Chrome'], // development
singleRun: false,
restartOnFileChange: true,
});
};
// ── angular.json — coverage thresholds ───────────────────────────────────
// "test": {
// "options": {
// "codeCoverage": true,
// "codeCoverageExclude": [
// "src/environments/**",
// "src/main.ts",
// "**/*.module.ts"
// ]
// }
// }
// To add thresholds, add a custom karma config entry:
// karma.conf.js coverageReporter:
coverageReporter: {
check: {
global: {
statements: 80,
branches: 70,
functions: 85,
lines: 80,
},
// Per-file thresholds (optional, stricter):
each: {
statements: 60,
branches: 50,
},
},
},
// ── GitHub Actions CI configuration ──────────────────────────────────────
// .github/workflows/test.yml
// name: Angular Tests
// on: [push, pull_request]
// jobs:
// test:
// runs-on: ubuntu-latest
// steps:
// - uses: actions/checkout@v4
// - uses: actions/setup-node@v4
// with: { node-version: '20' }
// - run: npm ci
// - run: npx ng test --watch=false --browsers=ChromeHeadless --code-coverage
// - uses: codecov/codecov-action@v4
// with: { files: ./coverage/lcov.info }
// ── Running tests locally ─────────────────────────────────────────────────
// Development (watch mode):
// ng test
// CI (single run, headless, with coverage):
// ng test --watch=false --browsers=ChromeHeadless --code-coverage
// Run specific spec file:
// ng test --include='**/posts-api.service.spec.ts'
random: true in Jasmine configuration randomises test execution order within each describe block. This catches tests that depend on execution order — a common source of intermittent CI failures. A test that passes when run alone but fails in the suite often has an order dependency. With random ordering, these dependencies surface locally during development rather than appearing as flaky CI failures. Use seed: '12345' to reproduce a specific failure by running with the same seed that caused it.--code-coverage flag causes Karma to instrument the TypeScript source for coverage tracking, which significantly slows down the compile step. Do not run coverage in watch mode during development — it makes the feedback loop 2-3x slower. Only enable coverage in CI (ng test --watch=false --code-coverage). In development, use ng test (no coverage) for fast feedback during TDD.Common Mistakes
Mistake 1 — Running tests without ChromeHeadless in CI (no display server)
❌ Wrong — ng test --watch=false without --browsers=ChromeHeadless in CI; Chrome cannot open a window; tests fail.
✅ Correct — always --browsers=ChromeHeadless in CI scripts; Chrome runs without display.
Mistake 2 — Coverage thresholds too high from day one (blocks new code without tests)
❌ Wrong — 100% threshold on a new project with legacy code; every PR that adds untested infrastructure code fails CI.
✅ Correct — start at current coverage level (e.g., 60%), enforce it as a floor, and raise it incrementally as coverage improves.