« Building Muscular Graphical Editors in Flex (BFUG March 2009) | Main | Velocity and Spring Web MVC »

JUnit, Jetty, HtmlUnit, and Automated Testing

I'm a firm believer in automated testing. The more testing you can automate the less work you need to do to be sure you didn't break stuff each time you make a change. While most people think of unit testing when talking about automated testing, the higher up the interaction chain you can go while not making your tests brittle the better off you will be. On my current project unit test coverage is great, but given that ultimately all of the services get exposed as a web application, being able to automate testing at that level would be beneficial. Below is the approach I took to automate testing our web application.

The key ingredients to the approach are JUnit (testing framework), HtmlUnit (web request handler), and Jetty (integrated application server). Each automated test is written as a JUnit test with a call to a centralized function to startup the web application. To fully isolate each test method you could switch this to an @Before annotation. I've found though that the automated tests are most accurately defined as integration tests. Having a single instance running for all tests simulates the ultimate environment better so I only ever startup one server. Each test method is responsible for setting up the environment (.i.e. test data) to match what it needs so that the order of test methods never matters.

public class SampleTest {
    @BeforeClass
    public static void startServer() throws Exception {
        ObjectMother.startServer();
    }
}

The startServer() call in ObjectMother is responsible for starting the server if not already running. I startup the server on a random port to test that the configuration isn't port specific. The base URL for the web application is exposed so that tests making requests know what URL to start the call at. One issue I ran into is that you might need to play with the physical directory passed to WebAppContext as running from Eclipse versus Ant might have you starting in different working directories. To get around this issue there is a a little directory helper that searches for the target directory based on current, parent, and a couple other locations it could be found out.

import org.mortbay.jetty.Server;
import org.mortbay.jetty.webapp.WebAppContext;
public class ObjectMother {
    public static String baseUrl;
    private static Server server;

    public static void startServer() throws Exception {
        if (server == null) {
            server = new Server(0);
            server.addHandler(new WebAppContext("build/testapp", "/sample-server"));
            server.start();

            int actualPort = server.getConnectors()[0].getLocalPort();
            baseUrl = "http://localhost:" + actualPort + "/sample-server";
        }
    }
}

The creation of the "build/testapp" referenced above is done by Ant. When running within Eclipse an external tool builder is called to stage the web application like it would if it were to package it as a war. In particular the staging done for unit testing adjusts any deployment parameters so that they tuned for the test environment.

Now that the server is running requests can be made against it. This is where HtmlUnit gets used. A typical test looks something like this:

@Test
public void testBasic() throws FailingHttpStatusCodeException, IOException
{
    List<NameValuePair> parameters = new ArrayList<NameValuePair>();
    parameters.add(new NameValuePair(SampleCommand.CUSTOMER, ObjectMother.CUSTOMER));
    parameters.add(new NameValuePair(SampleCommand.URI, ObjectMother.URI));
    WebRequestSettings webRequestSettings = new WebRequestSettings(new URL(ObjectMother.baseUrl + "/sample/"));
    webRequestSettings.setRequestParameters(parameters);
    WebClient webClient = new WebClient();
    webClient.setThrowExceptionOnFailingStatusCode(false);
    Page page = webClient.getPage(webRequestSettings);

    assertEquals(HttpServletResponse.SC_OK, page.getWebResponse().getStatusCode());
    // additional request specific assertions
}

Frequently the response is XML based, so for these the XmlPage wrapper is used instead.

assertEquals(ServerUtil.XML_CONTENT_TYPE, page.getWebResponse().getContentType());

XmlPage xmlPage = (XmlPage) page;
NodeList nodeList = xmlPage.getXmlDocument().getElementsByTagName(ObjectMother.FAILURE_ELEMENT_NAME);
assertTrue(nodeList.getLength() > 0);

Since Jetty is starting up within the JVM, instead of as a separate process, using Eclipse's debug as JUnit test just works. The web application startup catches Spring configuration errors quickly and ensures that all paths are what they should be.

Tags: htmlunit java jetty junit testing

Comments

Really useful blog thanks a lot!