Automated Testing in the Pipeline
A pipeline that doesn't run your test suite isn't really "CI", it's just automated building. This lesson covers making tests a real, fast, reliable gate in your pipeline.
Fail fast
jobs:
ci:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: '20' }
- run: npm ci
- run: npm run lint
- run: npm run typecheck
- run: npm test
Steps run in order and stop at the first failure by default, if npm run lint fails, npm test never runs, no point spending minutes running a full test suite against code that doesn't even pass linting.
Caching dependencies
Reinstalling every dependency from scratch on every single run wastes minutes per build, adding up fast across a busy repository. Caching the package manager's download cache (not node_modules itself, which can hide subtle bugs if reused stale) speeds this up significantly:
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
setup-node's built-in cache: 'npm' option automatically caches and restores npm's download cache between runs, keyed on your lockfile, install still runs, but mostly from local cache instead of re-downloading everything.
Test matrices
To verify your code works across multiple versions or environments, define a matrix, GitHub Actions runs the job once per combination automatically:
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: ['18', '20', '22']
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- run: npm ci && npm test
This runs the exact same job three times, once per Node version, in parallel, catching version-specific breakage before a user does.
WARNING
A flaky test (one that sometimes fails for no code-related reason, a timing issue, a shared test database) is worse than a slow test, it trains your team to re-run failed pipelines out of habit instead of trusting red as "something is actually wrong". Fix or quarantine flaky tests aggressively.