Integration Testing with Maven
Chapter 8
8.1 Introduction
This chapter covers integration testing with Maven.
8.2. Running a Selenium Test
8.2.1. Task
You need to write a Selenium test to test a web application or a web site.
8.2.2. Action
Selenium is a very straightforward way to test a web application, you can write a unit test in any number of languages and then use the Selenium Remote Control to automate a browser and perform assertions about the presence of text or UI elements in a page. This recipe uses a TestNG test which includes the appropriate Selenium code to test a well known web site: http://www.twitter.com. In a Maven project devoted to integration testing, the following test class connects to a Twitter user page and verifies the correct user name.
Example 8.1. TwitterTest a Selenium Test written in TestNG
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 TwitterTest {
private Selenium selenium;
@BeforeClass
public void startSelenium() {
this.selenium = new DefaultSelenium("localhost", 4444, "*safari", "http://www.twitter.com");
this.selenium.start();
}
@Test
@Parameters( { "user", "name" })
public void testTwitter(String user, String name) {
try {
selenium.open("http://www.twitter.com/" + user);
selenium.waitForPageToLoad("3000");
assert selenium.isTextPresent(name);
} catch (SeleniumException e) {
throw e;
}
}
@AfterClass(alwaysRun = true)
public void stopSelenium() {
this.selenium.stop();
}
}
The following POM contains the following configuration to configure the Selenium server and the Selenium integration tests:
- The Maven Surefire plugin is configured to skip tests. This will prevent the Surefire plugin from executing any unit tests during the test phase.
- An execution of the Surefire plugin is configured not to skip tests during the integration-test phase. This configuration coupled with the previous configuration simply moves the execution of the tests into the integration-test phase of the lifecycle.
- The user and name properties that the testTwitter() method relies upon are configured via the systemProperties configuration parameter of the Maven Surefire plugin.
- The Selenium Maven plugin's start-server goal is configured to run in the pre-integration-test phase. This has the effect of starting Selenium before the Surefire plugin executes the integration tests. No configuration options are passed to the start-server goal so Selenium will be started on a default port of 4444.
- A dependency on selenium-java-client-driver exposes the Selenium API to the test cases and allows the Surefire test to interact with the Selenium server.
Example 8.2. POM configuring SureFire to Execute Selenium Remote Control
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>selenium-reddit</artifactId>
<packaging>jar</packaging>
<version>1.0-SNAPSHOT</version>
<name>selenium-reddit</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-surefire-plugin</artifactId>
<configuration>
<skip>true</skip>
<systemProperties>
<property>
<name>user</name>
<value>tobrien</value>
</property>
<property>
<name>name</name>
<value>Tim O'Brien</value>
</property>
</systemProperties>
</configuration>
<executions>
<execution>
<phase>integration-test</phase>
<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>
<goals>
<goal>start-server</goal>
</goals>
<configuration>
<background>true</background>
</configuration>
</execution>
</executions>
</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>
</dependencies>
</project>
If you run Maven to the integration-test phase, Maven will start the Selenium server before it executes the integration-test phase. In the output shown below, the section that starts with selenium:start-server is the pre-integration-test execution of the start-server goal in the Selenium Maven plugin. This is followed by an invocation of the test goal from the Maven Surefire plugin which will execute the TestNG tests which rely on the Selenium server.
[INFO] Scanning for projects...
[INFO] ------------------------------------------------------------------------
[INFO] Building selenium-reddit
[INFO] task-segment: [integration-test]
[INFO] ------------------------------------------------------------------------
[INFO] [resources:resources {execution: default-resources}]
[WARNING] Using platform encoding (MacRoman actually) to copy filtered resources, i.e. build is platform dependent!
[INFO] Copying 0 resource
[INFO] [compiler:compile {execution: default-compile}]
[INFO] Nothing to compile - all classes are up to date
[INFO] [resources:testResources {execution: default-testResources}]
[WARNING] Using platform encoding (MacRoman actually) to copy filtered resources, i.e. build is platform dependent!
[INFO] Copying 0 resource
[INFO] [compiler:testCompile {execution: default-testCompile}]
[INFO] Compiling 1 source file to /Users/Tim/Library/Code/sonatype/ maven-cookbook/mcookbook-examples/integrate/selenium-reddit/ target/test-classes
[INFO] [surefire:test {execution: default-test}]
[INFO] Tests are skipped.
[INFO] [jar:jar {execution: default-jar}]
[INFO] Building jar: /Users/Tim/Library/Code/sonatype/maven-cookbook/ mcookbook-examples/integrate/selenium-reddit/ target/selenium-reddit-1.0-SNAPSHOT.jar
[INFO] [selenium:start-server {execution: default}]
Created dir: /Users/Tim/Library/Code/sonatype/maven-cookbook/ mcookbook-examples/integrate/selenium-reddit/target/selenium
Launching Selenium Server
Waiting for Selenium Server...
[INFO] User extensions: /Users/Tim/Library/Code/sonatype/maven-cookbook/ mcookbook-examples/integrate/selenium-reddit/ target/selenium/user-extensions.js
23:41:46,726 INFO [SeleniumServer] Java: Apple Inc. 14.1-b02-90
23:41:46,727 INFO [SeleniumServer] OS: Mac OS X 10.6.1 x86_64
23:41:46,735 INFO [SeleniumServer] v1.0.1 [2697], with Core
23:41:46,829 INFO [HttpServer] Version Jetty/5.1.x
23:41:46,830 INFO [Container] Started HttpContext[/selenium-server/driver, /selenium-server/driver]
23:41:46,832 INFO [Container] Started HttpContext[/selenium-server, /selenium-server]
23:41:46,832 INFO [Container] Started HttpContext[/,/]
23:41:46,846 INFO [SocketListener] Started SocketListener on 0.0.0.0:4444
23:41:46,846 INFO [Container] Started org.mortbay.jetty.Server@4a4e79f1
23:41:47,262 INFO [Credential] Checking Resource aliases
Selenium Server started
[INFO] [surefire:test {execution: default}]
[INFO] Surefire report directory: /Users/Tim/Library/Code/sonatype/ maven-cookbook/mcookbook-examples/integrate/selenium-reddit/ target/surefire-reports
-------------------------------------------------------
T E S T S
-------------------------------------------------------
Running TestSuite
23:41:48,178 INFO [org.mortbay.util.Credential] Checking Resource aliases
23:41:48,184 INFO [org.openqa.selenium.server.SeleniumDriverResourceHandler]
Command request: getNewBrowserSession[*safari, http://www.twitter.com, ] on session null
23:41:48,187 INFO [org.openqa.selenium.server.BrowserSessionFactory]
creating new remote session
23:41:48,436 INFO [org.openqa.selenium.server.BrowserSessionFactory]
Allocated session 2a66870a3c894a60a19ef6f2e7a1dc74 for http://www.twitter.com, launching...
23:41:48,519 INFO [org.openqa.selenium.server.browserlaunchers.
SafariCustomProfileLauncher] Launching Safari to visit...
23:41:53,251 INFO [org.openqa.selenium.server.SeleniumDriverResourceHandler]
Got result: OK, on session
23:41:53,265 INFO [org.openqa.selenium.server.SeleniumDriverResourceHandler]
Command request: open[http://www.twitter.com/tobrien, ] on session
23:41:56,231 INFO [org.openqa.selenium.server.SeleniumDriverResourceHandler]
Got result: OK on session
23:41:56,256 INFO [org.openqa.selenium.server.SeleniumDriverResourceHandler]
Command request: waitForPageToLoad[3000, ] on session
23:41:56,270 INFO [org.openqa.selenium.server.SeleniumDriverResourceHandler]
Got result: OK on session
23:41:56,284 INFO [org.openqa.selenium.server.SeleniumDriverResourceHandler]
Command request: isTextPresent[Tim O'Brien, ] on session
23:41:56,343 INFO [org.openqa.selenium.server.SeleniumDriverResourceHandler]
Got result: OK,true on session
23:41:56,352 INFO [org.openqa.selenium.server.SeleniumDriverResourceHandler]
Command request: testComplete[, ] on session
23:41:56,370 INFO [org.openqa.selenium.server.SeleniumDriverResourceHandler]
Got result: OK on session
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 8.609 sec
Results :
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 18 seconds
[INFO] Finished at: Fri Nov 27 23:41:56 CST 2009
[INFO] Final Memory: 32M/80M
[INFO] ------------------------------------------------------------------------
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.
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.
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>

<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>

<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>

<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>

<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>

<phase>post-integration-test</phase>
<goals>
<goal>stop</goal>
</goals>
</execution>
</executions>
<configuration>

<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>

<artifactId>sample-web</artifactId>
<version>1.0-SNAPSHOT</version>
<type>war</type>
</dependency>
</dependencies>
</project>
This POM contains the following configuration:
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.
[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