Code Coverage with JUnit and JaCoCo

Illustration for Code Coverage with JUnit and JaCoCo
By Last updated:

Testing is not just about writing assertions — it’s also about knowing how much of your application has been tested. That’s where code coverage comes in. Code coverage tools like JaCoCo (Java Code Coverage) measure how much of your code is executed when your test suite runs. This helps identify untested areas and improve overall test quality.

In this tutorial, we’ll explore how to use JUnit 5 with JaCoCo to measure and improve test coverage, integrate it with Maven/Gradle, and apply best practices for meaningful coverage.


Why Code Coverage Matters

  • Identify Gaps: Detect untested methods, branches, and edge cases.
  • Improve Reliability: Ensure critical paths are covered by tests.
  • Support Refactoring: High coverage builds confidence in code changes.
  • CI/CD Integration: Enforce coverage thresholds in pipelines.
  • Maintainability: Prevent untested code from creeping into production.

Think of code coverage like security cameras in a building — they don’t prevent theft, but they show you where you’re blind.


Types of Code Coverage

  1. Line Coverage: Measures executed lines of code.
  2. Branch Coverage: Ensures all conditional paths are tested.
  3. Method Coverage: Tracks executed methods.
  4. Class Coverage: Measures percentage of tested classes.

JaCoCo supports all of these out of the box.


Setting Up JaCoCo with Maven

Add the JaCoCo plugin to your pom.xml:

<plugin>
  <groupId>org.jacoco</groupId>
  <artifactId>jacoco-maven-plugin</artifactId>
  <version>0.8.10</version>
  <executions>
    <execution>
      <goals>
        <goal>prepare-agent</goal>
      </goals>
    </execution>
    <execution>
      <id>report</id>
      <phase>verify</phase>
      <goals>
        <goal>report</goal>
      </goals>
    </execution>
  </executions>
</plugin>

Run:

mvn clean test
mvn verify

JaCoCo generates reports in target/site/jacoco/index.html.


Setting Up JaCoCo with Gradle

Add to build.gradle:

plugins {
    id 'jacoco'
}

jacocoTestReport {
    reports {
        xml.required = true
        html.required = true
    }
}

Run:

gradle test jacocoTestReport

Reports are available in build/reports/jacoco/test/html.


Example: Measuring Coverage with JUnit 5

import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;

class Calculator {

    int add(int a, int b) { return a + b; }
    int subtract(int a, int b) { return a - b; }
}

class CalculatorTest {

    @Test
    void shouldAddNumbers() {
        Calculator calc = new Calculator();
        assertEquals(5, calc.add(2, 3));
    }

    @Test
    void shouldSubtractNumbers() {
        Calculator calc = new Calculator();
        assertEquals(1, calc.subtract(3, 2));
    }
}

JaCoCo will show 100% coverage for both methods.


Coverage with Mockito

Mockito helps ensure error paths are tested.

import org.junit.jupiter.api.Test;
import static org.mockito.Mockito.*;

class OrderServiceTest {

    interface PaymentGateway {
        boolean processPayment(double amount);
    }

    @Test
    void shouldTestPaymentFailure() {
        PaymentGateway gateway = mock(PaymentGateway.class);
        when(gateway.processPayment(anyDouble())).thenReturn(false);

        boolean result = gateway.processPayment(100.0);
        assert !result;
    }
}

Mockito ensures both success and failure paths are covered.


Coverage with Testcontainers

Integration tests with Testcontainers increase coverage of real-world scenarios.

import org.junit.jupiter.api.Test;
import org.testcontainers.containers.PostgreSQLContainer;

class DatabaseCoverageTest {

    @Test
    void shouldStartDatabase() {
        try (PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15")) {
            postgres.start();
            assert postgres.isRunning();
        }
    }
}

This validates containerized dependencies.


Enforcing Coverage Thresholds

Maven Example

<configuration>
  <rules>
    <rule>
      <element>BUNDLE</element>
      <limits>
        <limit>
          <counter>LINE</counter>
          <value>COVEREDRATIO</value>
          <minimum>0.80</minimum>
        </limit>
      </limits>
    </rule>
  </rules>
</configuration>

This enforces 80% minimum line coverage.


Best Practices

  • Focus on critical paths and business logic.
  • Don’t chase 100% coverage blindly.
  • Include negative tests (exceptions, invalid inputs).
  • Separate unit vs integration coverage.
  • Review JaCoCo reports regularly.

Version Tracker

  • JUnit 4 → JUnit 5: More flexible annotations and parameterized tests improve coverage.
  • Mockito Updates: Added static/final method mocking, covering previously hard-to-test code.
  • Testcontainers Growth: Broader ecosystem increases integration coverage across databases and message brokers.

Conclusion & Key Takeaways

Code coverage with JUnit 5 and JaCoCo provides visibility into tested vs untested code. By integrating JaCoCo into Maven/Gradle builds and CI/CD pipelines, teams can enforce meaningful coverage and ensure production reliability.

Key Takeaways:

  • Use JaCoCo for coverage reports.
  • Focus on quality, not just numbers.
  • Combine with Mockito and Testcontainers for comprehensive coverage.
  • Enforce thresholds in pipelines for consistency.

FAQ

1. What is JaCoCo used for?
JaCoCo measures code coverage in Java projects.

2. Can JaCoCo enforce minimum coverage?
Yes, via rules in Maven/Gradle configurations.

3. Does 100% coverage mean bug-free code?
No, it only means all lines are executed, not that all logic is correct.

4. Can I use JaCoCo with JUnit 5?
Yes, JaCoCo fully supports JUnit 5.

5. How do I view coverage reports?
Open the generated HTML reports in your browser.

6. Can Mockito improve coverage?
Yes, by simulating dependencies and failure scenarios.

7. Do integration tests count in coverage?
Yes, all executed code contributes to coverage metrics.

8. How do I add JaCoCo to CI/CD pipelines?
Run JaCoCo reports during build and enforce thresholds.

9. Can Testcontainers help with coverage?
Yes, they allow real-world integration testing.

10. Should I migrate from Cobertura to JaCoCo?
Yes, JaCoCo is actively maintained and better supported.