Video Thumbnail for Lesson
10.2: Unified Test Workflow

Designing a Unified Test Workflow

We start automation by authoring .github/workflows/test.yaml. The first iteration focuses on a single service (the Node API) and layers in best practices:

  1. Checkout & toolchain setup – Use actions/checkout and actions/setup-node (pinned by commit) to download code and install the appropriate runtime. The node-version-file and cache-dependency-path inputs point at package.json/package-lock.json so the runner respects the engines declared in the project.
  2. Task installation – Install the Task CLI via the generic setup-binary action, enabling us to call the standardized task install-ci and task test targets.
  3. Local feedback with Act – Create a Taskfile under .github/workflows/test/ that wraps act workflow-dispatch. This gives us tight feedback loops without pushing to GitHub.

Once the single-service version works, expand it using a matrix strategy:

strategy:
  fail-fast: false
  matrix:
    service:
      - services/node/api-node
      - services/go/api-golang
      - services/react/client-react
      - services/python/load-generator-python

Conditional steps (using startsWith(matrix.service, 'services/node'), etc.) install only the toolchains each service needs. Adding React and Python simply requires extending the matrix and reusing the same Task targets. This refactor eliminates copy/paste jobs while keeping runtime-specific configuration in one place.

Filtering Work with Determinators

Running every service on each change is wasteful. To scope test execution, introduce an upstream filter job powered by dorny/paths-filter. The job:

  1. Checks out the repository so it can diff the branch against the default branch.
  2. Reads .github/utils/file-filters.yaml, which maps each service to the glob patterns (source files, migrations, and workflow definitions) that should trigger its jobs.
  3. Emits a JSON array of touched services via the job output.

The test matrix then consumes that output through fromJson(needs.filter.outputs.services); if the array is empty, the matrix job skips entirely. Because Act lacks pull-request metadata, we pass a synthetic event.json with the default branch and reuse a local GitHub token so the filter action can hit the API.