GitLab merge requests can display JUnit test results directly within the UI, transforming raw XML output into actionable feedback.

Imagine a typical CI/CD pipeline. A job runs, tests execute, and somewhere in the artifacts, there’s a junit.xml file. Without GitLab’s integration, you’d download that file, unzip it, and manually parse it to see what failed. This feature automates that.

Here’s how it works:

  1. Test Execution: Your CI/CD job runs your test suite (e.g., using Maven, Gradle, pytest, Jest).
  2. Report Generation: Configure your test runner to output results in the JUnit XML format. This is a standard format that many testing frameworks support.
    • Maven: Ensure your pom.xml has the Surefire or Failsafe plugin configured to generate XML reports.
      <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-surefire-report-plugin</artifactId>
          <version>3.0.0-M5</version>
          <configuration>
              <reportsDirectory>${project.build.directory}/surefire-reports</reportsDirectory>
              <outputName>junit</outputName> <!-- This is important for GitLab -->
          </configuration>
      </plugin>
      
      Maven Surefire typically outputs to target/surefire-reports/TEST-*.xml. The outputName setting in the plugin configuration can influence the generated filename, but GitLab looks for a specific pattern.
    • Gradle: In your build.gradle or build.gradle.kts:
      // build.gradle (Groovy)
      test {
          useJUnitPlatform()
          reports {
              junitXml.enabled = true
              html.enabled = false
          }
      }
      
      Gradle outputs to build/test-results/test/TEST-*.xml.
    • Python (pytest):
      pytest --junitxml=report.xml
      
      This command directly creates a report.xml file.
    • Node.js (Jest):
      // jest.config.js
      module.exports = {
        reporters: [
          "default",
          ["jest-junit", { outputDirectory: "reports", outputName: "junit.xml" }]
        ],
      };
      
      You’ll need to install jest-junit: npm install --save-dev jest-junit.
  3. Artifact Archiving: The CI/CD job must archive the generated JUnit XML files as artifacts. This makes them available for GitLab to process.
    • .gitlab-ci.yml:
      test_job:
        stage: test
        script:
          - mvn test
          - echo "Tests executed."
        artifacts:
          when: always # Or on_failure
          reports:
            junit: "target/surefire-reports/TEST-*.xml" # For Maven Surefire
            # junit: "build/test-results/test/TEST-*.xml" # For Gradle
            # junit: "report.xml" # For pytest
            # junit: "reports/junit.xml" # For Jest
      
      The key here is the artifacts:reports:junit keyword. GitLab specifically looks for this to identify which artifacts contain JUnit reports. The path specified should match where your test runner outputs the XML files. You can use wildcards (*).

Once these steps are in place, when a pipeline runs for a commit associated with a merge request, GitLab will automatically parse the archived JUnit reports.

The results appear in several places:

  • Merge Request Widget: A summary at the top of the merge request shows if tests passed or failed.
  • "Tests" Tab: A dedicated tab on the merge request page lists all test suites and individual tests, indicating their status and providing error messages or stack traces for failures.
  • Commit Details: For individual commits within the merge request, the "Tests" tab will also show results.

This integration is powerful because it brings test feedback directly into the code review process. Reviewers can see exactly which tests broke and why, without leaving the GitLab interface. It dramatically speeds up the feedback loop and helps catch regressions earlier.

The underlying mechanism is that GitLab’s CI/CD runner, upon completing a job, scans the specified junit artifact paths. If it finds XML files matching the JUnit format, it parses them. The runner then uploads this parsed data to the GitLab server, which then renders it in the merge request UI. It’s not just a passive display; GitLab’s backend actively processes and stores this structured test data.

A common point of confusion is the exact path specified in .gitlab-ci.yml. It must precisely match the output directory and filename pattern of your test runner. If your runner outputs to build/test-results/test/ and the files are named TEST-mytest.xml, then junit: "build/test-results/test/TEST-*.xml" is correct. If you use outputName in Gradle or Maven, ensure that the junit path in .gitlab-ci.yml reflects that. GitLab looks for files that look like JUnit reports, but it needs the correct path to find them.

The next step after seeing your JUnit results reliably displayed is often integrating code coverage reports, which also have a similar artifact reporting mechanism in GitLab.

Want structured learning?

Take the full Gitlab-ci course →