DEV Community

Devanshu Biswas
Devanshu Biswas

Posted on

A CI Pipeline That Tests Itself: GitHub Actions With a JUnit Test That Parses the Workflow YAML

An earlier stage of the OrderHub project built the deploy artifacts by hand — eight Docker images, k8s manifests, a Helm chart — each one compiled and checked on a laptop. Day 45 automates all of it: a .github/workflows/ci.yml that runs the whole Maven reactor on every push, matrix-builds the eight service images, and — very much in the spirit of the project — ships a JUnit test that tests the workflow itself.

Two jobs: a gate and a fan-out

A GitHub Actions pipeline is jobs made of steps on fresh Ubuntu runners. The build-test job is the gate: check out, set up Temurin JDK 21 with a Maven cache, run mvn -B clean verify (all 163 tests), and upload the test reports.

jobs:
  build-test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-java@v4
        with:
          distribution: temurin
          java-version: '21'          # matches <java.version>21</java.version>
          cache: maven                # restore/save ~/.m2 keyed on pom hashes
      - run: mvn -B clean verify       # compile + all 163 tests, every module
      - uses: actions/upload-artifact@v4
        if: always()                   # keep reports even when the build failed
        with:
          name: surefire-reports
          path: '**/target/surefire-reports/**'
Enter fullscreen mode Exit fullscreen mode

Two details do the heavy lifting. cache: maven keys ~/.m2 on the pom hashes, so an unchanged dependency tree restores in seconds instead of re-downloading the world. And if: always() uploads the surefire reports even on a red build — the one time you most want to read them.

The matrix builds eight images from one job

The docker-build job needs: build-test, so images only build on a green reactor. It's gated to pushes on master, and a strategy.matrix fans the single step-set out into eight parallel jobs — one per service:

  docker-build:
    needs: build-test                                   # only on a green reactor
    if: github.event_name == 'push'
        && github.ref == 'refs/heads/master'            # push-to-master only
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        module: [config-server, eureka-server, api-gateway, order-service,
                 inventory-service, payment-service, shipping-service, notification-service]
    steps:
      - uses: actions/checkout@v4
      - uses: docker/setup-buildx-action@v3
      - uses: docker/build-push-action@v6
        with:
          context: .
          file: ${{ matrix.module }}/Dockerfile          # the multi-stage build
          push: false                                    # BUILD-ONLY — publish nothing
          tags: orderhub/${{ matrix.module }}:${{ github.sha }}
Enter fullscreen mode Exit fullscreen mode

push: false keeps the pipeline honest — it proves every image still builds without needing a registry. Flip it to true and add a commented login step to release. On a pull request, the whole docker-build job is skipped by its if: guard, so PR feedback stays fast while build-test still runs the full JDK-21 verify as a merge gate.

The workflow has a test

You can't run GitHub Actions on the build box, but you can parse the YAML and assert it's wired correctly. A plain JUnit + SnakeYAML CiWorkflowTest reads ci.yml and pins the pipeline's contract:

class CiWorkflowTest {                       // parses .github/workflows/ci.yml (SnakeYAML)

  @Test void workflowTriggersOnPushPullRequestAndDispatch() {
    assertTrue(on.containsKey("push"));       // (on: reads as Boolean.TRUE — handled)
    assertTrue(on.containsKey("pull_request"));
    assertTrue(on.containsKey("workflow_dispatch"));
  }
  @Test void buildTestJobRunsMavenVerifyOnJdk21WithMavenCache() { /* ... */ }
  @Test void dockerBuildDependsOnBuildTestAndMatrixesTheEightModules() {
    assertEquals("build-test", dockerBuild.get("needs"));   // scalar or list
    // strategy.matrix lists exactly the 8 service modules
  }
  @Test void noHardCodedSecretsOnlyExpressionReferences() {
    // every secrets.NAME must sit inside an open ${{ … }} expression — never a pasted literal
  }
}
Enter fullscreen mode Exit fullscreen mode

There's a genuine gotcha buried here: GitHub's on: key reads as the YAML 1.1 boolean true under SnakeYAML, so the test looks the triggers node up under either the "on" string or Boolean.TRUE. The four assertions pin the triggers, the JDK-21 cached-Maven verify gate, the needs + matrix image build, and the no-hard-coded-secrets rule — so the pipeline can't quietly rot. The reactor grows from 159 tests to 163, BUILD SUCCESS.

The build moves off the laptop: a red test or a broken Dockerfile is now caught before merge, automatically, and the pipeline config is reviewed in the same PR as the code it guards.

Step through the live pipeline run, flip the trigger to a pull request to watch the image job get skipped, and read the full anatomy here: https://dev48v.infy.uk/orderhub/day45-cicd.html

Top comments (0)