The Maven Cookbook

8.3. Running Integration Tests Against a Servlet Container

8.3. Running Integration Tests Against a Servlet Container

8.3.1. Task

You are testing a web application and you need to start a servlet container prior to running integration tests.

8.3.2. Action

Use the Maven Jetty Plugin to start an instance of a server prior to running your integration tests. Assume that you are writing integration tests to test the sample web application project that was introduced in Section 6.1, “Running a Web Application in a Servlet Container”. If you have some Selenium tests for the web application, you can start an instance of Jetty running the web application as a daemon in the pre-integration-test phase and stop the instance in the post-integration-test phase.

Your web application is in the org.sonatype.mcookbook:sample-web project and your integration tests are in the org.sonatype-mcookbook:sample-web-it project. You can configure the Maven Dependency plugin to copy the WAR to the sample-web-it project during the package phase, then during the pre-integation-test phase you will configure the Maven Jetty plugin to start an instance of Jetty to run the sample-web.war as well as to start the Selenium server. The Maven Surefire plugin will then execute the unit tests, and after the tests are complete the Jetty server will be stopped in the post-integration-test phase.

Our TestNG integration tests is as follows. It is going to connect to the default Jetty host and port of localhost:8080, and submit a form that calculates the tenth number in the Fibonacci sequence (F10). This integration test is stored in ${basedir}/src/test/java.

package org.sonatype.mcookbook;

import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Parameters;
import org.testng.annotations.Test;

import com.thoughtworks.selenium.DefaultSelenium;
import com.thoughtworks.selenium.Selenium;
import com.thoughtworks.selenium.SeleniumException;

public class FibTest {

private Selenium selenium;

  @BeforeClass
  public void startSelenium() {
    this.selenium = new DefaultSelenium("localhost", 4444, "*safari",
        "http://localhost:8080");
    this.selenium.start();
  }

  @Test public void testSequence() throws Exception {
    selenium.open("/sample-web/");
    selenium.type("index", "10");
    selenium.click("//input[@value='Calculate']");
    selenium.waitForPageToLoad("30000");
    assert selenium.isTextPresent("55");
  }
  
  @AfterClass(alwaysRun = true)
  public void stopSelenium() {
    this.selenium.stop();
  }

}

The POM to configure the sample-web-it is shown below.

<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 
           http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>org.sonatype.mcookbook</groupId>
  <artifactId>sample-web-it</artifactId>
  <packaging>jar</packaging>
  <version>1.0-SNAPSHOT</version>
  <name>sample-web-it</name>
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <configuration>
          <source>1.5</source>
          <target>1.5</target>
        </configuration>
      </plugin>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-dependency-plugin</artifactId>
        <executions>
          <execution>
            <id>copy</id>1
            <phase>package</phase>
            <goals>
              <goal>copy</goal>
            </goals>
            <configuration>
              <artifactItems>
                <artifactItem>
                  <groupId>org.sonatype.mcookbook</groupId>
                  <artifactId>sample-web</artifactId>
                  <version>1.0-SNAPSHOT</version>
                  <type>war</type>
                  <overWrite>true</overWrite>
                  <destFileName>sample-web.war</destFileName>
                </artifactItem>
              </artifactItems>
              <outputDirectory>
                ${project.build.directory}/war
              </outputDirectory>
              <overWriteReleases>true</overWriteReleases>
              <overWriteSnapshots>true</overWriteSnapshots>
            </configuration>
          </execution>
        </executions>
      </plugin>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-surefire-plugin</artifactId>
        <configuration>
          <skip>true</skip>
        </configuration>
        <executions>
          <execution>
            <phase>integration-test</phase>2
            <goals>
              <goal>test</goal>
            </goals>
            <configuration>
              <skip>false</skip>
            </configuration>
          </execution>
        </executions>
      </plugin>
      <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>selenium-maven-plugin</artifactId>
        <executions>
          <execution>
            <phase>pre-integration-test</phase>3
            <goals>
              <goal>start-server</goal>
            </goals>
            <configuration>
              <background>true</background>
            </configuration>
          </execution>
        </executions>
      </plugin>
      <plugin>
        <groupId>org.mortbay.jetty</groupId>
        <artifactId>maven-jetty-plugin</artifactId>
        <version>6.1.22</version>
        <executions>
          <execution>
            <id>start-jetty</id>4
            <phase>pre-integration-test</phase>
            <goals>
              <goal>run-war</goal>
            </goals>
            <configuration>
              <contextPath>sample-web</contextPath>
              <daemon>true</daemon>
              <webApp>
                ${project.build.directory}/war/sample-web.war
              </webApp>
            </configuration>
          </execution>
          <execution>
            <id>stop-jetty</id>5
            <phase>post-integration-test</phase>
            <goals>
              <goal>stop</goal>
            </goals>
          </execution>
        </executions>
        <configuration>6
          <stopPort>9991</stopPort>
          <stopKey>test</stopKey>
        </configuration>
      </plugin>
    </plugins>
  </build>
  <dependencies>
    <dependency>
      <groupId>org.testng</groupId>
      <artifactId>testng</artifactId>
      <version>5.10</version>
      <classifier>jdk15</classifier>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.seleniumhq.selenium.client-drivers</groupId>
      <artifactId>selenium-java-client-driver</artifactId>
      <version>1.0.1</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.sonatype.mcookbook</groupId>7
      <artifactId>sample-web</artifactId>
      <version>1.0-SNAPSHOT</version>
      <type>war</type>
    </dependency>
  </dependencies>
</project>

This POM contains the following configuration:

1

The Maven Dependency plugin is configured to copy the sample-web web application archive to the ${project.build.directory}/war/sample-web.war file. The copy goal is bound to the package phase.

2

The Surefire plugin is configured to skip all tests during the test phase and to execute all tests during the integration-test phase.

3

The Selenium server is started during the pre-integration-test phase as a background process. This makes the Selenium server available to any integration tests that need to interact with a browser.

4

The start-jetty execution starts the Jetty server in the pre-integration-test phase. This execution references the WAR downloaded by the Maven Dependency plugin and it also sets the context path to "sample-web". Setting daemon to "true" runs Jetty in the background and continues to progress through the Maven lifecycle. If daemon were set to "false", the Maven build would stop and wait for the Jetty process to complete.

5

The stop-jetty execution stops the Jetty server after the integration tests have been executed.

6

To stop the Jetty server we need to supply a stop port and a stop key. Both the run-war and stop goals of the Jetty plugin will use this configuration so it is defined at the plugin level instead of the execution level. The stop port defines the port number that Jetty will listen to for a stop command, and the stop key is a string that will be used by the jetty:stop goal to stop the Jetty process.

7

Adding a dependency to the web application project that is being tested will make sure that this project is ordered after the web application in a multi-module build.

When you run the integration-test phase of this build, you will see that Maven runs through the lifecycle and starts Selenium and Jetty before running the integration tests.

$ mvn clean install
[INFO] Scanning for projects...
[INFO] ------------------------------------------------------------------------
[INFO] Building sample-web-it
[INFO]    task-segment: [clean, integration-test]
[INFO] ------------------------------------------------------------------------
[INFO] [clean:clean {execution: default-clean}]
...
[INFO] [resources:resources {execution: default-resources}]
...
[INFO] [resources:testResources {execution: default-testResources}]
...
[INFO] [compiler:testCompile {execution: default-testCompile}]
[INFO] Compiling 1 source file to 
~/Code/sonatype/maven-cookbook/mcookbook-examples/integrate/sample-web-it/
target/test-classes
[INFO] [surefire:test {execution: default-test}]
[INFO] Tests are skipped.
[INFO] [jar:jar {execution: default-jar}]
...
[INFO] [dependency:copy {execution: copy}]
...
[INFO] [selenium:start-server {execution: default}]
Created dir: ~/maven-cookbook/mcookbook-examples/integrate/sample-web-it/
target/selenium
Launching Selenium Server
Waiting for Selenium Server...
...
[INFO] [dependency:copy {execution: copy}]
[INFO] Configured Artifact: org.sonatype.mcookbook:sample-web:1.0-SNAPSHOT:war
[INFO] Copying sample-web-1.0-SNAPSHOT.war to 
~/maven-cookbook/mcookbook-examples/integrate/sample-web-it/
target/war/sample-web.war
[INFO] [jetty:run-war {execution: start-jetty}]
[INFO] Configuring Jetty for project: sample-web-it
2009-11-29 04:21:37.994:INFO::Logging to STDERR via org.mortbay.log.StdErrLog
[INFO] Context path = /sample-web
[INFO] Starting jetty 6.1.22 ...
2009-11-29 04:21:38.683:INFO::Started SelectChannelConnector@0.0.0.0:8080
[INFO] Started Jetty Server
[INFO] [surefire:test {execution: default}]

-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Running TestSuite
04:21:39,436 INFO  [org.mortbay.util.Credential] Checking Resource aliases
...
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 3.597 sec

Results :

Tests run: 1, Failures: 0, Errors: 0, Skipped: 0

[INFO] [jetty:stop {execution: stop-jetty}]
[INFO] Stopping server 0
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 13 seconds
[INFO] Finished at: Sun Nov 29 04:21:42 CST 2009
[INFO] Final Memory: 48M/99M
[INFO] ------------------------------------------------------------------------
2009-11-29 04:21:43.201:INFO::Shutdown hook executing
2009-11-29 04:21:43.822:INFO::Shutdown hook complete