A GitHub Actions Workflow for Testing
Objectives
By the end of this lesson, you should be able to:
- Write a GitHub Actions workflow file, with the correct trigger, job, and step structure
- Explain what each part of the workflow,
on,jobs,steps, actually controls - Run a real Node.js test suite automatically on every push and pull request
💡 Why this matters: Course 3, Module 7 and 8 built a real, passing Jest and Supertest suite. This lesson is what actually runs it automatically, on every single push, rather than whenever someone remembers to run
npm testby hand.
⚠️ A note on verification: GitHub Actions runs on GitHub’s own infrastructure, it can’t execute inside this course’s own sandboxed tooling. The YAML below was validated with a real YAML parser, confirming it’s syntactically correct and structured the way described, its actual execution, a runner starting up, checking out code, and running tests, reflects GitHub Actions’ stable, current, documented behavior. Push this to a real GitHub repository to see it run.
A Workflow File
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm test
This file lives at .github/workflows/ci.yml, that specific path is what GitHub Actions watches for, any correctly formatted workflow file there is picked up automatically.
Reading It Piece by Piece
on: defines the triggers, here, every push to main, and every pull request targeting main, a workflow can trigger on many other events too, a schedule, a manual button press, a release being published. jobs: defines one or more jobs, each running independently, potentially in parallel, runs-on: ubuntu-latest picks the operating system the job actually runs on. steps: runs in order, within a job, uses: runs a pre-built action (actions/checkout@v4 fetches the repository’s code, actions/setup-node@v4 installs a specific Node.js version), run: executes a raw shell command directly.
Why Dependencies Are Cached
cache: 'npm' on the setup-node step caches installed node_modules between workflow runs, keyed by the lockfile’s contents, exactly the same layer-caching idea from Module 3’s Dockerfile, unchanged dependencies mean a much faster npm ci on the next run, instead of downloading everything from scratch every single time.
What Actually Runs, Automatically
Every push to main, and every pull request opened against it, triggers this workflow: a fresh Ubuntu machine starts, checks out the exact code from that push, installs Node.js 20, installs dependencies, and runs npm test, exactly Course 3’s Jest and Supertest suite, if any test fails, the workflow fails, visibly, on the pull request itself, before anyone needs to review the code manually to catch it.
Try It
- Write this workflow file, and place it at
.github/workflows/ci.ymlin a real Git repository with apackage.jsonand atestscript. - Push it to GitHub, open a pull request, and confirm the workflow runs automatically, visible directly on the pull request.
- Deliberately break a test, push that change, and confirm the workflow fails, and that failure is visible before the change is merged.
- Explain, in one or two sentences, what
cache: 'npm'actually speeds up, and why it’s keyed by the lockfile specifically.
Recap
- A workflow file at
.github/workflows/defines triggers (on), jobs, and steps, GitHub Actions picks it up and runs it automatically. uses:runs a pre-built action,run:executes a raw shell command, both are valid steps within a job.- This workflow runs Course 3’s real test suite on every push and pull request, catching a broken change before it merges, exactly what continuous integration means in practice.
Next lesson: extending this workflow to build and publish Module 3’s Docker image once tests pass, continuous deployment on top of continuous integration.