Automated testing is the foundation that makes continuous deployment trustworthy — this guide covers integrating different test types into your CI pipeline effectively.
Why Automated Testing in CI Matters
Without automated testing gating deployments, CI/CD just automates the speed at which bugs reach production — genuine confidence in automated deployment (see How to Set Up Zero-Downtime Deployments with Docker) depends on trustworthy automated test coverage catching regressions before they ship.
Types of Tests to Include
| Test Type | Purpose | Typical Speed |
|---|---|---|
| Unit tests | Individual function/component correctness | Fast (seconds) |
| Integration tests | Components working together correctly | Moderate |
| End-to-end tests | Full user workflows through the actual application | Slower |
Running Unit Tests in GitHub Actions
name: CI
on: [push]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm install
- run: npm test
Setting Up a Test Database for Integration Tests
services:
postgres:
image: postgres:16
env:
POSTGRES_PASSWORD: test
ports:
- 5432:5432
Most CI platforms support running service containers alongside your test job — providing a genuine, isolated database instance for integration tests rather than mocking database interactions entirely.
Running End-to-End Tests with a Real Browser
- run: npx playwright test
Tools like Playwright or Cypress can drive a real browser against your application in CI, testing genuine user workflows — typically run against a deployed staging environment or a temporarily-started local instance within the CI job.
Failing the Pipeline on Test Failure
Ensure your CI configuration genuinely blocks deployment on test failure — a test suite that runs but doesn't actually gate the pipeline provides no real safety benefit, just false confidence.
Setting Up Test Coverage Reporting
- run: npm test -- --coverage
Track code coverage trends over time — useful context, though coverage percentage alone isn't a complete measure of test quality; don't optimize purely for the number without considering genuine test meaningfulness.
Balancing Test Speed and Coverage
Comprehensive end-to-end tests are valuable but slow; a common effective pattern runs fast unit tests on every commit, with slower integration/e2e tests running less frequently (before merge to main, or on a schedule) — balances feedback speed with coverage depth.
Running Tests in Parallel
strategy:
matrix:
shard: [1, 2, 3, 4]
For large test suites, splitting tests across parallel CI runners significantly reduces total pipeline duration — most CI platforms support some form of test sharding/parallelization.
Handling Flaky Tests
A test that intermittently fails without a genuine code issue (a "flaky" test) undermines trust in your test suite — investigate and fix flaky tests promptly rather than accepting them as background noise, since repeated false failures train teams to ignore genuine failures too.
Common Errors
Tests pass locally but fail consistently in CI — usually an environment difference (missing environment variable, different dependency version, timezone/locale difference); ensure your CI environment closely mirrors your actual application's runtime environment.
Continue Reading
- How to Build a Simple CI/CD Pipeline with GitHub Actions
- How to Set Up Zero-Downtime Deployments with Docker
- CI/CD Pipeline Security Best Practices
Browse more articles in DevOps & CI/CD.