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 testor./gradlew testsuccessfully 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:
-Bruns Maven in batch mode (no interactive prompts)cache: mavenspeeds up subsequent runsif: 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=UTCandLANG=en_US.UTF-8in 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:
- Make sure
mvn test(or./gradlew test) works locally. - Add a CI configuration file for your platform (
ci.yml,.gitlab-ci.yml,Jenkinsfile, etc.). - Pin the JDK version and cache dependencies.
- Run
mvn -B teston every push and pull request. - Publish the Surefire XML reports so failures are visible in the CI UI.
- 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.
