github-actions
GitHub Actions Node.js Tests
GitHub Actions workflow to run Node.js tests across multiple versions with caching, coverage reports, and status checks.
Overview
A GitHub Actions workflow for running Node.js tests on every push and pull request. Tests across multiple Node.js versions, caches dependencies, uploads coverage reports, and provides clear status checks for pull request reviews.
Configuration
# .github/workflows/test.yml
name: Tests
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
# Cancel previous runs for the same branch/PR
concurrency:
group: test-${{ github.ref }}
cancel-in-progress: true
jobs:
test:
name: Node ${{ matrix.node-version }}
runs-on: ubuntu-latest
strategy:
fail-fast: false # Don't cancel other versions if one fails
matrix:
node-version: [18, 20, 22] # Test across LTS and current versions
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: npm
- name: Install dependencies
run: npm ci
- name: Run linting
run: npm run lint
- name: Run type checking
run: npx tsc --noEmit
- name: Run tests with coverage
run: npm test -- --ci --coverage --reporters=default --reporters=jest-junit
env:
JEST_JUNIT_OUTPUT_DIR: ./reports
JEST_JUNIT_OUTPUT_NAME: test-results.xml
- name: Upload coverage to Codecov
if: matrix.node-version == 20 # Only upload once
uses: codecov/codecov-action@v4
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: ./coverage/lcov.info
fail_ci_if_error: false
- name: Upload test results
if: always() # Upload even if tests fail
uses: actions/upload-artifact@v4
with:
name: test-results-node-${{ matrix.node-version }}
path: |
./reports/
./coverage/
retention-days: 7
# Separate job for integration tests
integration:
name: Integration Tests
runs-on: ubuntu-latest
needs: test # Run after unit tests pass
services:
postgres:
image: postgres:16-alpine
env:
POSTGRES_USER: test
POSTGRES_PASSWORD: test
POSTGRES_DB: testdb
ports:
- 5432:5432
options: >-
--health-cmd "pg_isready -U test"
--health-interval 10s
--health-timeout 5s
--health-retries 5
redis:
image: redis:7-alpine
ports:
- 6379:6379
options: >-
--health-cmd "redis-cli ping"
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
- run: npm ci
- name: Run integration tests
run: npm run test:integration
env:
DATABASE_URL: postgresql://test:test@localhost:5432/testdb
REDIS_URL: redis://localhost:6379
Key Options Explained
fail-fast: false— If Node 18 tests fail, Node 20 and 22 tests still run. Useful for identifying version-specific issues.cache: npm— Caches the npm global cache directory between runs, significantly speeding upnpm ci.--ciflag — Tells Jest to run in CI mode: no interactive prompts, fails on snapshot mismatches, and optimizes for single-run execution.services— Spins up PostgreSQL and Redis containers alongside the test runner. Accessible vialocalhoston the mapped ports.if: always()— Uploads test artifacts even when tests fail, so you can inspect reports and coverage from failing runs.needs: test— Integration tests wait for all unit test matrix jobs to complete successfully before starting.
Common Modifications
- Vitest: Replace
npm test -- --ci --coveragewithnpx vitest run --coverageand adjust the reporter settings. - pnpm or yarn: Change
cache: npmtocache: pnpmandnpm citopnpm install --frozen-lockfile. - Database migrations: Add
npx prisma migrate deployornpm run db:migratebefore integration tests. - E2E tests: Add a Playwright or Cypress job with
npx playwright install --with-depsand browser-specific runners. - Required checks: Configure branch protection rules to require the
testjob to pass before merging PRs.