How do I run JUnit tests in CI/CD pipelines?

Running JUnit tests automatically in a CI/CD pipeline ensures every commit is verified before it reaches production. The core idea is simple: your CI server checks out the code, builds the project, and runs mvn test (or gradle test) — the same commands you use locally.

Below are practical setups for the most common CI/CD platforms.

1. Prerequisites

Before wiring up CI/CD, make sure your project already:

  • Uses Maven Surefire or Gradle to run JUnit 5 tests
  • Follows standard test naming conventions (*Test.java, *Tests.java)
  • Runs mvn test or ./gradlew test successfully on your local machine

If mvn test doesn’t work locally, it won’t work in CI either.

2. GitHub Actions

Create .github/workflows/ci.yml:

name: CI

on:
  push:
    branches: [ main, develop ]
  pull_request:
    branches: [ main ]

jobs:
  test:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout source
        uses: actions/checkout@v4

      - name: Set up JDK 25
        uses: actions/setup-java@v4
        with:
          distribution: 'temurin'
          java-version: '25'
          cache: maven

      - name: Run tests with Maven
        run: mvn -B test

      - name: Upload test report
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: surefire-reports
          path: '**/target/surefire-reports/*.xml'

Key points:

  • -B runs Maven in batch mode (no interactive prompts)
  • cache: maven speeds up subsequent runs
  • if: always() uploads reports even if tests fail — critical for debugging

3. GitLab CI

Create .gitlab-ci.yml:

image: maven:3.9-eclipse-temurin-25

variables:
  MAVEN_OPTS: "-Dmaven.repo.local=.m2/repository"

cache:
  paths:
    - .m2/repository

stages:
  - test

unit-tests:
  stage: test
  script:
    - mvn -B test
  artifacts:
    when: always
    reports:
      junit:
        - '**/target/surefire-reports/TEST-*.xml'
    paths:
      - '**/target/surefire-reports/'
    expire_in: 1 week

GitLab automatically renders JUnit XML reports in the merge request UI when you declare them under reports.junit.

4. Jenkins (Declarative Pipeline)

Create a Jenkinsfile in the project root:

pipeline {
    agent any

    tools {
        jdk 'jdk-25'
        maven 'maven-3.9'
    }

    stages {
        stage('Checkout') {
            steps { checkout scm }
        }

        stage('Test') {
            steps {
                sh 'mvn -B test'
            }
        }
    }

    post {
        always {
            junit '**/target/surefire-reports/*.xml'
        }
    }
}

The junit step in post.always publishes results to Jenkins’ Test Result view, whether the build passed or failed.

5. CircleCI

Create .circleci/config.yml:

version: 2.1

jobs:
  test:
    docker:
      - image: cimg/openjdk:25.0
    steps:
      - checkout
      - restore_cache:
          keys:
            - v1-deps-{{ checksum "pom.xml" }}
      - run:
          name: Run tests
          command: mvn -B test
      - save_cache:
          paths:
            - ~/.m2
          key: v1-deps-{{ checksum "pom.xml" }}
      - store_test_results:
          path: target/surefire-reports

workflows:
  version: 2
  build-and-test:
    jobs:
      - test

6. Failing the Build Correctly

By default, both Maven and Gradle exit with a non-zero status when tests fail, which causes the CI job to fail. Do not override that:

<!-- ❌ Never do this in CI -->
<plugin>
    <artifactId>maven-surefire-plugin</artifactId>
    <configuration>
        <testFailureIgnore>true</testFailureIgnore>
    </configuration>
</plugin>

If tests are ignored, broken code sneaks into main.

7. Publishing Test Reports

Surefire always produces XML reports here:

target/surefire-reports/TEST-*.xml

Every CI platform can read this format:

Platform How to publish
GitHub Actions actions/upload-artifact or a test-reporter action
GitLab CI artifacts.reports.junit
Jenkins junit '**/target/surefire-reports/*.xml'
CircleCI store_test_results
Azure Pipelines PublishTestResults@2 task with testResultsFormat: JUnit

8. Running Selective Tests in CI

For faster pipelines, split tests by JUnit tag:

mvn -B test -Dgroups=fast

Then run a separate slower stage for integration tests:

mvn -B verify -Dgroups=slow

This lets you get quick feedback on PRs while still running the full suite before merging.

9. Common Pitfalls

  • Different Java version in CI — always pin the JDK explicitly (java-version: '25').
  • Time-zone / locale issues — set TZ=UTC and LANG=en_US.UTF-8 in the job environment.
  • Flaky tests — never mask them with retries; fix them or tag them as @Tag("flaky") and quarantine.
  • No dependency cache — cache ~/.m2/repository (Maven) or ~/.gradle/caches (Gradle) or your pipeline will be needlessly slow.

Summary

To run JUnit tests in CI/CD:

  1. Make sure mvn test (or ./gradlew test) works locally.
  2. Add a CI configuration file for your platform (ci.yml, .gitlab-ci.yml, Jenkinsfile, etc.).
  3. Pin the JDK version and cache dependencies.
  4. Run mvn -B test on every push and pull request.
  5. Publish the Surefire XML reports so failures are visible in the CI UI.
  6. Let non-zero exit codes fail the build — never ignore test failures.

Once this pipeline is in place, every commit is automatically verified, and broken tests block merges before they can reach production.