Jacoco Coverage for Maven Multi-Module Project
Code coverage is a crucial aspect of software development, helping us ensure that our code is thoroughly tested. JaCoCo (Java Code Coverage) is a popular tool for measuring code coverage in Java applications. This article will explore how to set up maven multi module code coverage using JaCoCo in a Spring Boot project.
1. Understanding Maven Multi-Module Projects
A Maven multi-module project is like a big project made up of smaller projects. The main project collects and organizes these smaller projects, making it easier to handle and keep things neat. It’s a way of building software in a structured and organized way, where each small part has its role in the overall project. This structure is beneficial for large-scale applications, promoting code reuse, maintainability, and efficient project management.
1.1 Setting Up a Maven Multi-Module Project
In this article, we will assume a modular project structure with two modules: core
and web
. We can use the Spring Initializr or create it manually. Let’s go through the steps to set up a Maven multi-module project in Spring Boot.
1.1.1 Step 1: Create the Parent Project with Spring Initializer
Start by visiting the Spring Initializer web interface. We can configure the parent project and its basic settings by following these steps:
- Set the
Project
toMaven Project
. - Choose the appropriate
Language
(Java for this project). - Set the
Spring Boot
version. - In the
Project Metadata
section, provide theGroup
,Artifact
, andName
for your parent project. - Check the
Packaging
asJar
. Later we will change the packaging type to POM. - Add dependencies as per your project requirements (
Spring Web
for this project).
Click on the Generate button to download the generated project ZIP file.
1.1.2 Step 2: Extract and Navigate
Once downloaded, extract the ZIP file to a directory of your choice. Open a terminal and navigate into the project directory:
cd jacoco-parent-project
Note: We need to modify the pom.xml
file to change the packaging type to POM. Open the pom.xml
file in the root directory of the project and locate the <packaging>
. Change the value from jar or war to pom.
Now, we have a Spring Boot project with the packaging type as pom. This project can be used as the parent project in a multi-module setup.
1.1.3 Step 3: Create Submodules
Using the terminal, let’s create submodules for different parts of our Spring Boot application. For example, let’s create two submodules, core
and web
:
mvn archetype:generate -DgroupId=com.jcg.core -DartifactId=core -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false mvn archetype:generate -DgroupId=com.jcg.web -DartifactId=web -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false
1.1.4 Step 4: Verify or Modify Parent POM
Open the pom.xml file in the parent project directory and add the following <modules> section if not present. The parent pom.xml file should be like this:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.jcg</groupId> <artifactId>jacoco-parent-project</artifactId> <version>0.0.1-SNAPSHOT</version> <name>jacoco-parent-project</name> <packaging>pom</packaging> <description>Demo project for Maven Multi-Module Project</description> <modules> <module>core</module> <module>web</module> </modules> <properties> <java.version>17</java.version> <spring.boot.version>3.2.0</spring.boot.version> </properties> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.10.1</version> <configuration> <source>${java.version}</source> <target>${java.version}</target> </configuration> </plugin> <!-- Maven Surefire plugin for running tests --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>3.0.0-M5</version> </plugin> </plugins> </build> </project>
2. Project Example
2.1 Core Module Layer
First, Let’s create the core
module, which contains the business logic.
package com.jcg.core; public class Calculator { public int add(int a, int b) { return a + b; } }
In this example, The Calculator
class provides a simple method, add
which takes two integers as parameters and returns their sum. This class will be used in CalculatorController
within the web
module, demonstrating the separation of concerns in a multi-module Spring Boot project.
2.1.1 Unit Test – Core Module Test
Now, we will create a test class for the Calculator
class in the core
module.
public class CalculatorTest { @Test public void testAdd() { Calculator calculator = new Calculator(); int result = calculator.add(2, 3); assertEquals(5, result); } }
2.1.2 Ensure Core Module is Covered – Surefire Plugin Configuration
Make sure the core
module is covered by adding the maven-surefire-plugin
to its pom.xml
. This will employ the Maven Surefire plugin to execute the unit tests.
<!-- core/pom.xml --> <build> <!-- Maven Surefire plugin for running unit test --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>3.1.2</version> </plugin> </plugins> </build>
2.2 Web Module Layer
Create the web
module, which contains the Spring Boot application.
@RestController @RequestMapping("/calculator") public class CalculatorController { private final Calculator calculator; @Autowired public CalculatorController(Calculator calculator) { this.calculator = calculator; } @GetMapping("/add") public int add(@RequestParam int a, @RequestParam int b) { return calculator.add(a, b); } }
This controller provides a simple RESTful endpoint for adding two numbers. In this example:
@RestController
is used to indicate that this class is a controller that handles HTTP requests and returns JSON responses.@RequestMapping("/calculator")
specifies the base path for all endpoints in this controller.- The constructor is annotated with
@Autowired
to inject an instance of theCalculator
into the controller. - The
add
method is mapped to the/add
endpoint and takes two parameters (a
andb
) from the request parameters. It delegates the addition operation to the injectedCalculator
instance and returns the result.
2.2.1 Integration Test – Web Module Test
Let’s create a test class for the CalculatorController
class in the web
module.
@WebMvcTest(CalculatorController.class) public class CalculatorControllerTest { @Autowired private MockMvc mockMvc; @Mock private Calculator calculator; @Test public void testAdd() throws Exception { when(calculator.add(anyInt(), anyInt())).thenReturn(5); int a = 2; int b = 3; mockMvc.perform(get("/calculator/add") .param("a", String.valueOf(a)) .param("b", String.valueOf(b))) .andExpect(status().isOk()) .andExpect(content().string("5")); } }
In this example, we used @WebMvcTest
to focus on testing the CalculatorController
class. In this test:
@WebMvcTest(CalculatorController.class)
is used to focus the test on theCalculatorController
class.- We injected a
MockMvc
instance to perform HTTP requests and assert the responses. @Mock
is used to mock theCalculator
dependency.- The
testAdd
method performs a GET request to the/calculator/add
endpoint with parametersa=2
andb=3
. - The behavior of the
calculator.add
method is mocked to return5
. - Assertions are made to ensure that the response status is OK and the response content is “5”.
This test ensures that the CalculatorController
correctly interacts with the Calculator
and produces the expected response.
2.2.2 Maven Failsafe Plugin for Integration Test
Update the web module pom.xml to manage the execution of integration tests during the build.
<build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-failsafe-plugin</artifactId> <version>3.1.2</version> <executions> <execution> <goals> <goal>integration-test</goal> <goal>verify</goal> </goals> </execution> </executions> </plugin> </plugins> </build>
3. Configuring JaCoCo for Multi-Module Coverage
To configure the JaCoCo plugin for multi-module coverage in our Maven project, we need to include the JaCoCo plugin configuration in the pom.xml
of the parent project. This allows us to aggregate code coverage reports from multiple modules. Below is an example of how to set up JaCoCo to measure code coverage in a multi-module project.
3.1 Add JaCoCo Plugin to Parent POM
Include the JaCoCo plugin in the build/plugins
section of the parent pom.xml
:
<!-- JaCoCo Plugin for code coverage --> <plugin> <groupId>org.jacoco</groupId> <artifactId>jacoco-maven-plugin</artifactId> <version>0.8.7</version> <!-- Adjust version as needed --> <executions> <execution> <id>prepare-agent</id> <goals> <goal>prepare-agent</goal> </goals> </execution> <execution> <id>report</id> <phase>prepare-package</phase> <goals> <goal>report</goal> </goals> </execution> </executions> </plugin>
The prepare-agent
goal is executed during the initialize
phase, and the report
goal is executed during the prepare-package
phase. This configuration ensures that JaCoCo is set up to collect code coverage data during the build.
3.2 Configure JaCoCo in Each Module
In each module’s pom.xml
, include the following in the build/plugins
section:
<!-- JaCoCo Plugin for code coverage --> <plugin> <groupId>org.jacoco</groupId> <artifactId>jacoco-maven-plugin</artifactId> <version>0.8.7</version> <executions> <execution> <goals> <goal>prepare-agent</goal> </goals> </execution> <execution> <id>report</id> <phase>prepare-package</phase> <goals> <goal>report</goal> </goals> </execution> </executions> </plugin>
This ensures that JaCoCo is configured for each submodule, and coverage reports are generated individually.
3.3 Running Tests and Generating Reports
Execute the following Maven command to build and generate code coverage reports:
mvn clean install
This command will compile the code, run tests, and generate JaCoCo reports.
3.4 Viewing JaCoCo Reports
After running the Maven command above, you can find the reports by navigating to the web/target/site/jacoco/index.html
and core/target/site/jacoco/index.html
file to view the code coverage report. The images below show the report generated for the core and web modules respectively.
3.5 Analyzing JaCoCo Reports
JaCoCo generates reports that include line coverage, branch coverage, and more. Let’s take a look at some essential metrics:
- Line Coverage: Indicates the percentage of lines covered by tests.
- Branch Coverage: Measures the coverage of decision points, such as if statements and switch statements.
- Instruction Coverage: Represents the percentage of Java bytecode instructions covered by tests.
4. Conclusion
In conclusion, integrating JaCoCo into Maven multi-module projects provides a powerful solution for ensuring code coverage across the entire codebase. The modular structure of Maven multi-module projects, combined with JaCoCo’s detailed reports, enables developers to identify and address areas of the application that lack proper test coverage. By following the steps outlined in this article and exploring the various JaCoCo configurations, we can enhance the quality and reliability of our Java projects.
5. Download the Source Code
This was an article on Jacoco Coverage for Maven Multi-Module Project.
You can download the full source code of this example here: Jacoco Coverage for Maven Multi-Module Project