Sensor Web, Geoprocessing, Security & GeoRM, Geostatistics, Semantics, 3D, Ilwis, Earth Observation

End-to-End Client-Server Integration Testing with Maven – Project Setup

March 7th, 2013 @ 09:35 by Matthes Rieke
Posted in Best Practices, Geoprocessing, Sensor Web

Preliminary Remark: This blog post targets Open Source developers and will basically cover technical details.

Automated Integration Testing for Java Web Services is an important pylon to ensure the stability of a web service and in particular its internal workflows. It is a very convenient way to ensure that the server is reacting just as expected in a set of pre-defined situations in terms of responses to specific requests. An easy way of defining integration tests is to provide a certain set of fully-defined requests (POST, GET, …) and corresponding responses. However, if you also are maintaining a client framework or library dedicated to that service, you are in the sweet situation of being able to kill two birds with one stone:

End-To-End Client-Server Integration Testing.

This is the first of two blog posts on End-To-End Integration Testing. For now, I am focussing on the Maven project setup. The follow-up will provide some insights on the actual implementation of integration tests – the client side.

In the following I will try to provide a guide on how to incorporate an integration testing component into your existing Maven-3-managed web service. I will use the 52°North Sensor Event Service (SES, server component) and the 52°North OX-Framework (OXF, client library) as a hook for the following sections. Knowledge of the following topics is helpful for understanding the article:

We will cover all needed steps to enable integration testing in your web service project. In particular, details on preparing a temporary webapp, reserving network ports dynamically, bootstrapping a servlet container and the actual execution of the integration tests will be illustrated.

Project Setup

Assuming that you already have a multi-module (=reactor) project, the integration testing (IT) can be easily integrated as a dedicated submodule of your parent pom.xml. This way, you ensure that it is very well encapsulated and also easy to find for a project newbie. For our SES, I have created a dedicated profile for integration testing. Therefore, integration testing will not be executed on every build, only by activation. The profile looks like this:

<profile>
    <id>integration-test</id>
    <activation>
        <property>
            <name>env</name>
            <value>ci</value>
        </property>
    </activation>
    <modules>
        <module>52n-ses-integration-test</module>
    </modules>
</profile>

The next step is to create the pom.xml of the newly established module. The main contents of that will be definitions of the plugins we will be using to prepare the webapp, launch the server, run tests, etc. The “dependencies” section will be rather short as you will only need your client library as a real dependency. So we will start with that:

<dependencies>
    <!-- common test mockup/convenience classes -->
    <dependency>
        <groupId>org.n52.sensorweb.ses</groupId>
        <artifactId>52n-ses-tests</artifactId>
        <scope>test</scope>
    </dependency>
    <!-- the submodule of the OXF for the SES -->
    <dependency>
        <groupId>org.n52.sensorweb</groupId>
        <artifactId>oxf-ses-adapter</artifactId>
    </dependency>
</dependencies>

The remainder of the pom.xml will be plugins and their assignment to the correct lifecycle phase. The first thing we need to do is to prepare our on-the-fly webapp directory. We will do so by extracting the module artifact that creates the web archive (.WAR file). For the SES this is the 52n-ses-core module. For the extraction we will use the maven-dependency-plugin‘s unpack goal:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-dependency-plugin</artifactId>
    <executions>
        <execution>
            <id>unpack</id>
            <phase>test</phase>
            <goals>
                <goal>unpack</goal>
            </goals>
            <configuration>
                <artifactItems>
                    <artifactItem>
                        <groupId>org.n52.sensorweb.ses</groupId>
                        <artifactId>52n-ses-core</artifactId>
                        <version>${project.version}</version>
                        <type>war</type>
                        <overWrite>true</overWrite>
                        <outputDirectory>
${project.build.directory}/${targetWebappName}
                        </outputDirectory>
                        <excludes>**/logback.xml</excludes>
                    </artifactItem>
                </artifactItems>
                <overWriteReleases>true</overWriteReleases>
                <overWriteSnapshots>true</overWriteSnapshots>
            </configuration>
        </execution>
    </executions>
</plugin>

This will unpack the .WAR which has been created in module prior to the integration testing module. Note that I excluded the logging configuration in order to replace it with a custom one (I will not show that, but the maven-resources-plugin should do the job). Also note that this could be used to pull the latest SNAPSHOT from a remote repository, enabling integration testing even from outside the actual server project.

So much for setting up our on-the-fly webapp directory. The next step is kind of sweet: let Maven (in particular the build-helper-maven-plugin) search for an open network port and reserve it for our build:

<plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>build-helper-maven-plugin</artifactId>
    <version>1.7</version>
    <executions>
        <execution>
            <id>reserve-ports</id>
            <phase>test</phase>
            <goals>
                <goal>reserve-network-port</goal>
            </goals>
            <configuration>
                <portNames>
                    <portName>jettyServerPort</portName>
                    <portName>jettyServerStopPort</portName>
                </portNames>
            </configuration>
        </execution>
    </executions>
</plugin>

Now we are finally ready to bootstrap our servlet container engine. I use the jetty-maven-plugin, but others (e.g. for Apache Tomcat) exist. The plugin can use the previously reserved ports via the portNames as a property:

<plugin>
    <groupId>org.mortbay.jetty</groupId>
    <artifactId>jetty-maven-plugin</artifactId>
    <version>8.1.5.v20120716</version>
    <configuration>
        <scanIntervalSeconds>10</scanIntervalSeconds>
        <stopPort>${jettyServerStopPort}</stopPort>
        <stopKey>STOP</stopKey>
        <webAppXml>${basedir}/src/test/resources/jetty-test.xml</webAppXml>
        <webAppSourceDirectory>
${project.build.directory}/${targetWebappName}
        </webAppSourceDirectory>
        <overrideWebXml>
${project.build.directory}/${targetWebappName}/WEB-INF/web.xml
        </overrideWebXml>
        <connectors>
            <connector
            implementation="org.eclipse.jetty.server.nio.SelectChannelConnector">
                <port>${jettyServerPort}</port>
                <maxIdleTime>60000</maxIdleTime>
            </connector>
        </connectors>
        <webAppConfig>
            <contextPath>/</contextPath>
        </webAppConfig>
        <systemProperties>
            <!-- This hack is to set catalina.base to the current jetty
             base directory while the plugin runs so log4j will log
             somewhere and not complain -->
            <systemProperty>
                <name>catalina.base</name>
                <value>${project.build.directory}</value>
            </systemProperty>
            <systemProperty>
                <name>catalina.home</name>
                <value>${project.build.directory}</value>
            </systemProperty>
        </systemProperties>
    </configuration>
    <executions>
        <execution>
            <id>start-jetty</id>
            <phase>package</phase>
            <goals>
                <goal>run</goal>
            </goals>
            <configuration>
                <scanIntervalSeconds>0</scanIntervalSeconds>
                <daemon>true</daemon>
            </configuration>
        </execution>
        <execution>
            <id>stop-jetty</id>
            <phase>post-integration-test</phase>
            <goals>
                <goal>stop</goal>
            </goals>
        </execution>
    </executions>
</plugin>

The webAppSourceDirectory parameter points to our extracted webapp (targetWebappName is a convenience property of the parent pom which is used to define the final name of the .WAR), overrideWebXml is needed as well, otherwise the plugin complains about a missing web.xml. Note the two executions: the first is for startup, the second one stops the jetty server again after all tests were executed.

Finally, we are ready to start our actual integration tests. You might wonder “How will my tests be aware of the previously dynamic created port?”. Because the final HTTP URL of our service is depending on the port number, we cannot use the maven-resources-plugin to filter a specific properties file. Here comes the replacer plugin into play. It provides capabilities to do token-value-replacments on specific files at every lifecycle phase imaginable, in our case this phase is prepare-package. We configure it to replace the dummy service URL with a concatenated property:

<plugin>
    <groupId>com.google.code.maven-replacer-plugin</groupId>
    <artifactId>replacer</artifactId>
    <version>1.5.2</version>
    <executions>
        <execution>
            <id>integration-test-ports-replace_prepare-package</id>
            <phase>prepare-package</phase>
            <goals>
                <goal>replace</goal>
            </goals>
            <configuration>
                <includes>
                    <include>
*/**/test-classes/**/integration-test-ports.properties
                    </include>
                </includes>
                <regex>false</regex>
                <replacements>
                    <replacement>
                        <token>sesInstance</token>
                        <value>
http://localhost:${jettyServerPort}/${targetWebappName}/services/Broker
                        </value>
                    </replacement>
                </replacements>
            </configuration>
        </execution>
    </executions>
</plugin>

As we have setup all needed properties, lets execute the tests. The maven-failsafe-plugin‘s (a fork of the maven-surefire-plugin dedicated to integration testing) default configuration looks for the following pattern: src/test/java/**/*IT.java. The configuration is very straightforward and also covers two executions – testing and verification separately. This is needed as we do not want our build to fail before we have shut down our Jetty. More on that later when I come to lifecycle management.

<plugin>
    <artifactId>maven-failsafe-plugin</artifactId>
    <version>2.12.3</version>
    <executions>
        <execution>
            <id>integration-test</id>
            <phase>integration-test</phase>
            <goals>
                <goal>integration-test</goal>
            </goals>
            <configuration>
                <skip>false</skip>
            </configuration>
        </execution>
        <execution>
            <id>verify</id>
            <phase>verify</phase>
            <goals>
                <goal>verify</goal>
            </goals>
            <configuration>
                <skip>false</skip>
            </configuration>
        </execution>
    </executions>
    <configuration>
        <skip>false</skip>
    </configuration>
</plugin>

This concludes the project setup. You should now have a pom.xml covering all necessary steps to execute maven-based integration tests. But I will provide some useful insights about the plugins and their lifecycle-based executions in the following section.

Plugins and their Lifecycle Execution

I forgot to mention that I have a packaging of jar on my integration module even if I do not need a final jar. This allows us to attach to every lifecycle phase even when executing “mvn clean install”. A “pom” packaging would only execute package, install and deploy by default – not enough for our rather complicated plugin configuration. Let me first outline the default Maven lifecycle and our attached plugins:

validate .. test-compile
process-test-classes
test maven-dependency-plugin:unpack, build-helper-maven-plugin:reserve-network-port
prepare-package replacer:replace
package jetty-maven-plugin:run
pre-integration-test
integration-test maven-failsafe-plugin:integration-test
post-integration-test jetty-maven-plugin:stop
verify maven-failsafe-plugin:verify
install
deploy

As you can see, I avoided using any lifecycle phase prior to process-test-classes. The reason for that lies in the run goal of the jetty-maven-plugin. “The plugin forks a parallel lifecycle to ensure that the “compile” phase has been completed before invoking Jetty” (source: jetty-maven-plugin documentation). This would affect in particular the reserve-network-port goal’s second execution, resulting in jetty booting up on different ports then populated to our test-classes. Additionally, it would block twice the ports actually needed for the build.

The lifecycle assignments somehow look a bit odd, but they do the job very well and as the module is only responsible for integration testing this should not cause to much headaches. In particular it ensures that all resources such as the Jetty server are freed even when there are test failures. If you use maven-surefire-plugin for integration testing the whole build would be cancelled whenever the first test fails.

52°North Projects

Currently, we have established Integration Testing for two of our projects: the WPS and the SES. Both projects are included in our Continous Integration server with Integration Testing enabled. Next up will probably be the SOS. Things are a bit more complicated there as it relies on a database layer. So, stay tuned for related blog posts!

Acknowledgments

Credits go out to Ivan Suftin and Tom Kunicki of USGS for pointing me to the replacer and the build-helper-maven-plugin.

Resources

0 Comments »

No comments yet.

RSS feed for comments on this post.

Leave a comment

Time limit is exhausted. Please reload CAPTCHA.