Integration testing with the Maven Cargo plugin

Testing user interfaces and successful deployment of web applications is a form of integration testing. In order to perform these tests it is necessary to automatically deploy your web application (WAR, EAR, ..) and its configuration artifacts (external properties, system arguments, ..) to some servlet container. One way of achieving this is by performing continuous delivery to a dedicated testing or staging environment.

In this article I will demonstrate an alternative technique which involves integration testing as part of your build pipeline using the Maven Cargo plugin [1]. The idea is to hook into the integration-test phase of the Maven Failsafe plugin which is by Maven convention the gateway to integration testing in the Maven lifecycle.

The setting that will serve as an example is as follows: our web application is built to a single WAR artifact that is deployed to a Tomcat servlet container. It is configured by properties files in a configuration directory outside of the application context. The location of the configuration directory is supplied by passing a JVM argument to the Tomcat instance.

Integrating automated container deployment

We begin by adding a dedicated profile for integration testing purposes. This can be useful to control when to perform integration tests as they tend to consume a lot more time than smoke tests or unit tests.

    <profiles>
        <profile>
            <id>integration-test</id>
            <activation>
                <activeByDefault>true</activeByDefault>
            </activation>
            <build>
                <plugins>
                    <plugin>
                        <artifactId>maven-failsafe-plugin</artifactId>
                    </plugin>
                    <plugin>
                        <groupId>org.codehaus.cargo</groupId>
                        <artifactId>cargo-maven2-plugin</artifactId>
                        <version>${cargo.plugin.version}</version>
                        <configuration>
                            <container>
                                <containerId>tomcat7x</containerId>
                                <zipUrlInstaller>
                                    <url>http://archive.apache.org/dist/tomcat/tomcat-7/v7.0.54/bin/apache-tomcat-7.0.54.zip</url>
                                    <downloadDir>${project.build.directory}/downloads</downloadDir>
                                    <extractDir>${project.build.directory}/extracts</extractDir>
                                </zipUrlInstaller>
                            </container>
                            <deployables>
                                <deployable>
                                    <properties>
                                        <context>ROOT</context>
                                    </properties>
                                </deployable>
                            </deployables>
                            <configuration>
                                <configfiles>
                                    <configfile>
                                        <file>${cargo.container.configuration.src.dir}/config1.properties</file>
                                        <todir>${cargo.container.configuration.to.dir}</todir>
                                    </configfile>
                                    <configfile>
                                        <file>${cargo.container.configuration.src.dir}/config2.properties</file>
                                        <todir>${cargo.container.configuration.to.dir}</todir>
                                    </configfile>
                                </configfiles>
                            </configuration>
                        </configuration>
                        <executions>
                            <execution>
                                <id>start-tomcat</id>
                                <phase>pre-integration-test</phase>
                                <goals>
                                    <goal>start</goal>
                                </goals>
                                <configuration>
                                    <configuration>
                                        <properties>
                                            <cargo.jvmargs>${cargo.container.jvmargs}</cargo.jvmargs>
                                        </properties>
                                    </configuration>
                                </configuration>
                            </execution>
                            <execution>
                                <id>stop-tomcat</id>
                                <phase>post-integration-test</phase>
                                <goals>
                                    <goal>stop</goal>
                                </goals>
                            </execution>
                        </executions>
                    </plugin>
                </plugins>
            </build>
        </profile>
    </profiles>

We begin by configuring the Maven Failsafe plugin and further use the Cargo plugin whose executions we bind to the pre-integration-test and post-integration-test phases respectively. The idea is to start the Tomcat during pre-integration-test and deploy our application and to gracefully shutdown the Tomcat during post-integration-test.

Firstly, we configure one or more containers. In this case we need only a Tomcat which we download directly from the Apache archive to target/downloads and extract it to target/extracts. Our deployable WAR artifact is deployed as ROOT in this example. The configfiles property is used to manually copy external artifacts into the application context. Finally, we can configure JVM arguments in the pre-integration-test phase by configuring the cargo.jvmargs property.

For reference, the properties section of our POM looks like this:

<properties>  
...
        <cargo.plugin.version>1.4.9</cargo.plugin.version>
        <cargo.container.configuration.src.dir>${basedir}/../configuration-test/src/main/resources/com/lukaspradel/example/configuration/test</cargo.container.configuration.src.dir>
        <cargo.container.configuration.to.dir>conf/properties/test</cargo.container.configuration.to.dir>
        <cargo.container.jvmargs>-D.configuration.dir=${cargo.container.configuration.to.dir}
            -Dcom.sun.management.jmxremote
            -Dcom.sun.management.jmxremote.port=9012
            -Dcom.sun.management.jmxremote.authenticate=false
            -Dcom.sun.management.jmxremote.ssl=false</cargo.container.jvmargs>
...
</properties>  

This integrates automatic setup of a container as well as deployment of our application to said container into our build pipeline. A list of supported containers can be found in the documentation of the Cargo plugin. All common containers are supported such as

  • Geronimo
  • Glassfish
  • JBoss
  • Jetty
  • Tomcat
  • WebLogic
  • WebSphere
  • Wildfly

Take a look at the official doc [2].

Basic smoke testing

By convention, all unit test classes whose names end with IT are considered integration tests by the Maven failsafe plugin. Hence, no further configuration is necessary, unless you wish to differentiate them further.

As a basic integration smoke test we will check if our application is deployed successfully and the Tomcat serves with HTTP code OK (200). An easy to use HTTPClient is the Apache httpclient:

<dependencies>  
...
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
            <version>${httpclient.version}</version>
            <scope>test</scope>
        </dependency>
...
</dependencies>  

Our smoke test looks as follows:

package com.lukaspradel.example.it;

import com.lukaspradel.example.it.common.AbstractIntegrationTest;  
import org.apache.http.HttpResponse;  
import org.apache.http.HttpStatus;  
import org.apache.http.client.ClientProtocolException;  
import org.apache.http.client.CookieStore;  
import org.apache.http.client.HttpClient;  
import org.apache.http.client.methods.HttpGet;  
import org.apache.http.client.protocol.HttpClientContext;  
import org.apache.http.impl.client.BasicCookieStore;  
import org.apache.http.impl.client.HttpClientBuilder;  
import org.testng.annotations.Test;

import java.io.IOException;

import static org.testng.Assert.assertEquals;

/**
 * Check if the application was deployed correctly during integration-test phase
 * and if it can be reached successfully.
 *
 * @author pradel
 *
 */
public class CheckApplicationDeploymentIT extends AbstractIntegrationTest {

    @Test
    public void testWebapplicationUp() throws ClientProtocolException, IOException {

        HttpClient client = HttpClientBuilder.create().build();
        CookieStore cookieStore = new BasicCookieStore();
        HttpClientContext context = HttpClientContext.create();
        context.setCookieStore(cookieStore);

        HttpGet httpget = new HttpGet(BASE_URL);

        HttpResponse response = client.execute(httpget, context);

        assertEquals(response.getStatusLine().getStatusCode(), HttpStatus.SC_OK);
    }
}

Here, we assume that AbstractIntegrationTest supplies something like

protected static final String BASE_URL = "http://localhost/";.

1. http://cargo.codehaus.org/Home

2. http://cargo.codehaus.org/Containers