Wednesday 27 January 2010

Automated Integration Testing with Maven, Selenium, Failsafe, Jetty and DBUnit

Most Maven users are aware that unit tests can be executed as part of the automated build process. Maven can also automate integration tests, which are run as part of the “integration-test” and “verify” phases of the Maven build process. This article shows how to automate the integration tests using a number of useful Maven plug-ins. Integration testing tests groups of software modules (as opposed to unit testing, which tests individual units). It is intended to catch problems that arise when modules developed in isolation are assembled and used together, which is often the case when an application is being built by a team of developers. It also encompasses testing components within a container, especially if that container has been “mocked” in the unit tests. For example, a Spring controller might be unit tested with the MockHttpServletRequest and MockHttpServletResponse within the spring-test module. Business logic such as Spring service classes might have been mocked as well. An integration test in this scenario would involve firing real HTTP requests at the Spring controller running inside an application server such as Jetty or Tomcat.

The first consideration is how to create integration tests. Depending on how you define them, they could be standard NUnit tests or they could be Cactus or HTTPUnit type tests which call code executing from within the application server. However, Selenium offers a simple alternative to these approaches that can be easily integrated with Maven. Selenium consists of an IDE (a Firefox add-on) that can be used to record and automate actions (such as clicking links and selecting options in a select box) made within the browser. These can be then exported in a number of formats, including as a JUnit test (in fact, a SeleneseTestCase which extends TestCase). I’m aware that this approach to integration testing verges on functional testing. However, I think the boundaries between unit, integration and functional testing are blurry If it makes you feel any better, pretend this blog is called "Automated Functional Testing with Maven, Selenium, Failsafe, Jetty and DBUnit".

Here’s quick overview of how it all works. There are plenty of good tutorials about Selenium and using the IDE to write tests, so I won’t go into it here. After you have recorded your test case in the IDE, you can export it in Java format.
public class MyITCase extends SeleneseTestCase {

public void setUp() throws Exception {
 setUp("http://localhost:8080/", "*firefox");
}

public void testMy() throws Exception {
 selenium.open("/myapp/login.htm");
 selenium.type("j_username", "percy.project-manager");
 selenium.type("j_password", "password");
 selenium.click("//button[@type='submit']");
 selenium.waitForPageToLoad("30000");
 selenium.click("link=Client list");
 selenium.waitForPageToLoad("30000");
 selenium.click("link=View");
 selenium.waitForPageToLoad("30000");

}

}
In order to play-back the SeleneseTestCase, the Selenium Maven plug-in is needed. This is a “server” which runs an instance of the browser to be remote controlled by the SeleneseTestCase. The plug-in starts the Selenium server in readiness for test cases during the pre-integration-test phase and stops it afterwards (in post-integration-test).

org.codehaus.mojo
selenium-maven-plugin
1.0

 
  start-pre-integration
  pre-integration-test
  
   start-server
  
  
   true
  
 
 
  stop
  post-integration-test
  
   stop-server
  
 


In addition to needing the Selenium server, you will need a method to fire off the integration tests during the “integration-test” phase of the Maven build. Obviously, you could use the Surefire plug-in for this. However, the configuration of the plug-in gets a bit more tricky as you need to exclude the unit tests from the integration test and vice verse – especially the integration tests during unit testing as the Selenium server (and Jetty) won’t be running during the unit test phase.

There is a way to do this by using inclusion and exclusion filters within the configuration of the Surefire plugin and I have seen several explanations of how to do this. However, there is a fork of the Surefire plugin called Failsafe which is designed for integration testing. By default, it only runs tests with that contain IT and ITCase in the name. The main benefit is that the Failsafe plugin will continue to run after a test failure or error. That may seem to be a bit strange, but it is intentional. It is to ensure that the post-integration-test phase is executed, which might perform vital clean-up and tear-down operations that should run after the tests are finished. Additionally, it seems to have an enhanced class loader architecture compared to Surefire to handle some more exotic test scenarios – but that’s not relevant to this discussion.

Here is the necessary configuration for the Failsafe plugin.

org.codehaus.mojo
failsafe-maven-plugin
2.4.3-alpha-1

 
  integration-test
  
   integration-test
  
 
 
  verify
  
   verify
  
 


Now we have created our integration tests, and have a means of playing them back during the integration-test phase of the Maven build. However, we also need to fire up an application server to run the application itself during the integration-test phase. For this, the Jetty plug-in is ideal. It can be configured to start during the pre-integration-test phase and stop during the post-integration-test phase.


org.mortbay.jetty
maven-jetty-plugin
6.1.16

 10
 8005
 STOP


 
  start-jetty
  pre-integration-test
  
   run
  
  
   0
   true
  
 
 
  stop-jetty
  post-integration-test
  
   stop
  
 


I recommend using the jetty:run goal rather than jetty:run-war or jetty:run-exploded (although both of these work too). That is because the latter two goals cause the unit tests to run a 2nd time when the application folders are assembled in the target/ directory. However, if you don’t mind sitting through a 2nd run through of your unit tests then this doesn’t matter.

At this point you might be happy to stop, but there is one final step you might wish to take which is to initialise your database prior to the integration-test phase. This can be done with the SQL Maven plug-in (to create the schema) and the DBUnit plug-in (to insert test data). These can both be configured to run during the pre-integration-test phase. The reason for doing this is that if you are using assertions in your Selenium case (i.e. assertTrue( isElementPresent(//div[@id=’content’]/table[@id=’results’]/tbody/tr[3]/td[2])); then you need to be certain that the data in the database is going to be correct with respect to these assertions.

org.codehaus.mojo
sql-maven-plugin
1.2

 
  com.microsoft
  sqljdbc
  1.2.0
 


 com.microsoft.sqlserver.jdbc.SQLServerDriver
 jdbc:sqlserver://mssql2005server:1433;databaseName=MyDatabase;SelectMethod=cursor
 secret
 squirrel


 
  create-schema
  pre-integration-test
  
   execute
  
  
   true
   
    src/main/database/schema-mssql.sql
   
  
 


DBUnit configuration example.

org.codehaus.mojo
dbunit-maven-plugin

 
  com.microsoft
  sqljdbc
  1.2.0
 


 com.microsoft.sqlserver.jdbc.SQLServerDriver
 jdbc:sqlserver://mssql2005server:1433;databaseName=MyDatabase;SelectMethod=cursor
 secret
 squirrel
 src/test/resources/web-test-data.xml
 MSSQL_CLEAN_INSERT
 flat


 
  pre-integration-test
  
   operation
  
 


Hope it helps!