1. Overview
Maven is the most popular build tool in the Java space, while integration testing is an essential part of the development process. Therefore, it’s a natural choice to configure and execute integration tests with Maven.
In this tutorial, we’ll go over a number of different ways to use Maven for integration testing and to separate integration tests from unit tests.
2. Preparation
To make the demonstration code close to a real-world project, we’ll set up a JAX-RS application. This application is deployed to a server before the execution of integration tests and dismantled afterward.
2.1. Maven Configuration
We’ll build our REST application around Jersey – the reference implementation of JAX-RS. This implementation requires a couple of dependencies:
<dependency>
<groupId>org.glassfish.jersey.containers</groupId>
<artifactId>jersey-container-servlet-core</artifactId>
<version>2.27</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.inject</groupId>
<artifactId>jersey-hk2</artifactId>
<version>2.27</version>
</dependency>
We can find the latest versions of these dependencies here and here.
We’ll use the Jetty Maven plugin to set up a testing environment. This plugin starts a Jetty server during the pre-integration-test phase of the Maven build lifecycle, then stops it in the post-integration-test phase.
Here’s how we configure the Jetty Maven plugin in pom.xml:
<plugin>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-maven-plugin</artifactId>
<version>9.4.11.v20180605</version>
<configuration>
<httpConnector>
<port>8999</port>
</httpConnector>
<stopKey>quit</stopKey>
<stopPort>9000</stopPort>
</configuration>
<executions>
<execution>
<id>start-jetty</id>
<phase>pre-integration-test</phase>
<goals>
<goal>start</goal>
</goals>
</execution>
<execution>
<id>stop-jetty</id>
<phase>post-integration-test</phase>
<goals>
<goal>stop</goal>
</goals>
</execution>
</executions>
</plugin>
When the Jetty server starts up, it’ll be listening on port 8999. The stopKey and stopPort configuration elements are used solely by the plugin’s stop goal and their value isn’t important from our perspective.
Here is where to find the latest version of the Jetty Maven plugin.
Another thing to notice is that we must set the packaging element in the pom.xml file to war, otherwise the Jetty plugin cannot start the server:
<packaging>war</packaging>
2.2. Creating a REST Application
The application endpoint is very simple – returning a welcome message when a GET request hits the context root:
@Path("/")
public class RestEndpoint {
@GET
public String hello() {
return "Welcome to Baeldung!";
}
}
This is how we register the endpoint class with Jersey:
package com.baeldung.maven.it;
import org.glassfish.jersey.server.ResourceConfig;
public class EndpointConfig extends ResourceConfig {
public EndpointConfig() {
register(RestEndpoint.class);
}
}
To have the Jetty server aware of our REST application, we can use a classic web.xml deployment descriptor:
<web-app
xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<servlet>
<servlet-name>rest-servlet</servlet-name>
<servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
<init-param>
<param-name>javax.ws.rs.Application</param-name>
<param-value>com.baeldung.maven.it.EndpointConfig</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>rest-servlet</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
</web-app>
This descriptor must be placed in the directory /src/main/webapp/WEB-INF** to be recognized by the server.
2.3. Client-Side Test Code
All test classes in the following sections contain a single method:
@Test
public void whenSendingGet_thenMessageIsReturned() throws IOException {
String url = "http://localhost:8999";
URLConnection connection = new URL(url).openConnection();
try (InputStream response = connection.getInputStream();
Scanner scanner = new Scanner(response)) {
String responseBody = scanner.nextLine();
assertEquals("Welcome to Baeldung!", responseBody);
}
}
As we can see, this method does nothing but sending a GET request to the web application we set up before and verifying the response.
3. Integration Testing in Action
An important thing to notice about integration testing is that test methods often take quite a long time to run.
As a result, we should exclude integration tests from the default build lifecycle, keeping them from slowing down the whole process each time we build a project.
A convenient way to separate integration tests is to use build profiles. This kind of configuration enables us to execute integration tests only when necessary – by specifying a suitable profile.
In the sections that follow, we’ll configure all integration tests with build profiles.
4. Testing With the Failsafe Plugin
The simplest way to run integration tests is to use the Maven failsafe plugin.
By default, the Maven surefire plugin executes unit tests during the test phase, while the failsafe plugin runs integration tests in the integration-test phase.
We can name test classes with different patterns for those plugins to pick up the enclosed tests separately.
The default naming conventions enforced by surefire and failsafe are different, thus we just need to follow these conventions to segregate unit and integration tests.
The execution of the surefire plugin includes all classes whose name starts with Test, or ends with Test, Tests or TestCase. In contrast, the failsafe plugin executes test methods in classes whose name starts with IT, or ends with IT or ITCase.
This is where we can find the documentation regarding test inclusion for surefire, and here is the one for failsafe.
Let’s add the failsafe plugin to the POM with default configuration:
<profile>
<id>failsafe</id>
<build>
<plugins>
<plugin>
<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>
</profile>
This link is where to find the latest version of the failsafe plugin.
With the above configuration, the following test method will be executed in the integration-test phase:
public class RestIT {
// test method shown in subsection 2.3
}
Since the Jetty server starts up in the pre-integration-test phase and shuts down in post-integration-test, the test we have just seen passes with this command:
mvn verify -Pfailsafe
We can also customize the naming patterns to include classes with different names:
<plugin>
<artifactId>maven-failsafe-plugin</artifactId>
<version>3.1.2</version>
<configuration>
<includes>
<include>**/*RestIT</include>
<include>**/RestITCase</include>
</includes>
</configuration>
...
</plugin>
5. Testing With the Surefire Plugin
Apart from the failsafe plugin, we can also use the surefire plugin to execute unit and integration tests in different phases.
Let’s assume we want to name all integration tests with the suffix IntegrationTest. Since the surefire plugin runs tests with such a name in the test phase by default, we need to exclude them from the default execution:
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.2</version>
<configuration>
<excludes>
<exclude>**/*IntegrationTest</exclude>
</excludes>
</configuration>
</plugin>
The latest version of this plugin is here.
We’ve taken all test classes having a name ending with IntegrationTest out of the build lifecycle. It’s time to put them back with a profile:
<profile>
<id>surefire</id>
<build>
<plugins>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.2</version>
<executions>
<execution>
<phase>integration-test</phase>
<goals>
<goal>test</goal>
</goals>
<configuration>
<excludes>
<exclude>none</exclude>
</excludes>
<includes>
<include>**/*IntegrationTest</include>
</includes>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
Instead of binding the test goal of the surefire plugin to the test build phase, as usual, we bound it to the integration-test phase. The plugin will then kick in during the integration testing process.
Notice that we must set an exclude element to none to override the exclusion specified in the base configuration.
Now, let’s define an integration test class with our naming pattern:
public class RestIntegrationTest {
// test method shown in subsection 2.3
}
This test will be running with the command:
mvn verify -Psurefire
6. Testing With the Cargo Plugin
We can use the surefire plugin with the Maven cargo plugin. This plugin comes with built-in support for embedded servers, which are very useful for integration testing.
More details about this combination can be found here.
7. Testing With JUnit’s @Category
A convenient way to selectively execute tests is to leverage the @Category annotation in the JUnit 4 framework. This annotation lets us exclude particular tests from unit testing, and include them in integration testing.
First off, we need an interface or class to work as a category identifier:
package com.baeldung.maven.it;
public interface Integration { }
We can then decorate a test class with the @Category annotation and Integration identifier:
@Category(Integration.class)
public class RestJUnitTest {
// test method shown in subsection 2.3
}
Rather than declaring the @Category annotation on a test class, we can also use it at the method level to categorize individual test methods.
Excluding a category from the test build phase is simple:
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.2</version>
<configuration>
<excludedGroups>com.baeldung.maven.it.Integration</excludedGroups>
</configuration>
</plugin>
Including the Integration category in the integration-test phase is also straightforward:
<profile>
<id>category</id>
<build>
<plugins>
<plugin>
<artifactId>maven-failsafe-plugin</artifactId>
<version>3.1.2</version>
<configuration>
<includes>
<include>**/*</include>
</includes>
<groups>com.baeldung.maven.it.Integration</groups>
</configuration>
<executions>
<execution>
<goals>
<goal>integration-test</goal>
<goal>verify</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
We can now run integration tests with a Maven command:
mvn verify -Pcategory
8. Adding a Separate Directory for Integration Tests
It’s desirable at times to have a separate directory for integration tests. Organizing tests this way allows us to entirely isolate integration tests from unit tests.
We can use the Maven build helper plugin for this purpose:
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<version>3.0.0</version>
<executions>
<execution>
<id>add-integration-test-source</id>
<phase>generate-test-sources</phase>
<goals>
<goal>add-test-source</goal>
</goals>
<configuration>
<sources>
<source>src/integration-test/java</source>
</sources>
</configuration>
</execution>
</executions>
</plugin>
Here is where we can find the latest version of this plugin.
The configuration we’ve just seen adds a test source directory to the build. Let’s add a class definition to that new directory:
public class RestITCase {
// test method shown in subsection 2.3
}
It’s time to run integration tests in this class:
mvn verify -Pfailsafe
The Maven failsafe plugin will execute methods in this test class due to the configuration we set in subsection 3.1.
A test source directory often goes with a resource directory. We can add such a directory in another execution element to the plugin configuration:
<executions>
...
<execution>
<id>add-integration-test-resource</id>
<phase>generate-test-resources</phase>
<goals>
<goal>add-test-resource</goal>
</goals>
<configuration>
<resources>
<resource>
<directory>src/integration-test/resources</directory>
</resource>
</resources>
</configuration>
</execution>
</executions>
9. Conclusion
This article went over using Maven to run integration tests with a Jetty server, focusing on the configuration of the Maven surefire and failsafe plugins.
The complete source code for this tutorial can be found over on GitHub.