| This article describes how to create, execute, and automate Web application tests using HttpUnit, an open-source, request/response-based testing tool built upon the JUnit test framework. We will cover how to use it, some of its limitations, and some advanced practices as they apply to Portal testing. This tutorial assumes basic familiarity with JUnit. |
Download the author's files associated with this article
|
Overview of HttpUnit
At the heart of HttpUnit is theWebConversation class. From the JavaDoc, "This class manages cookies used to maintain session context, computes relative URLs, and generally emulates the browser behavior needed to build an automated test of a Web site". A WebConversation will typically be your starting point when creating your test methods:
public void testHomePage() throws Exception
{
// Initiate WebConversation
WebConversation wc = new WebConversation();
// Get the home page of the portal
WebResponse homePage = wc.getResponse("http://www.bea.com");
...
}
|
WebResponse is HttpUnit's wrapper for the response from the server. It encapsulates the HTTP response headers, the numeric response code (200, 404, 500, etc.), and the body of the response. Most of your test logic will revolve around retrieving data from the WebResponse and verifying its correctness:
assertEquals(200, homePage.getResponseCode());
String[] copyrightContentValue = homePage.getMetaTagContent("name", "copyright");
assertTrue(copyrightContentValue.length == 1);
assertEquals("BEA Systems http://www.bea.com", copyrightContentValue[0]);
|
To navigate your site, you'll use a
WebLink object, which you can get using one of the various getLink() methods on WebResponse:
WebLink dev2devLink = homePage.getLinkWith("BEA dev2dev Online");
assertNotNull(dev2devLink);
WebResponse dev2devHomePage = dev2devLink.click();
assertEquals(200, dev2devHomePage.getResponseCode());
|
In addition to
getLink(), WebResponse has many helper methods for navigating the body of the response and/or verifying content, including getForm(), getTable(), and getImage(). Reference the JavaDoc for additional methods and descriptions, and check out the HttpUnit Cookbook for usage samples.Here's a simple example of how you might verify some of the content of your Web application. In it, we'll assert that the left navigation bar on the old version of BEA's dev2dev site is rendering all of the expected text links for the various products:
WebTable leftNavTable = dev2devHomePage.getTableStartingWithPrefix("Products");
String productsCell = leftNavTable.getCellAsText(0,0);
// getCellAsText() will transform <br> to CR/LF, and to a space.
// Therefore, instead of specifying what that whole messy string should look like,
// we'll just assert that each product's text link is present.
assertTrue(productsCell.indexOf("WebLogic Platform") > -1);
assertTrue(productsCell.indexOf("WebLogic Server") > -1);
assertTrue(productsCell.indexOf("WebLogic Workshop") > -1);
...
|
Limitations of HttpUnit
HttpUnit uses the Rhino: JavaScript for Java engine to execute the JavaScript functions in your Web application; unfortunately, not all are supported. A complete list of valid functions can be found here. Also, since Rhino is a different engine than the one implemented by your browser, the results of your functions could be different within HttpUnit, just as the results could vary between Internet Explorer and Netscape.In that same vein, since HttpUnit is acting as the client, it obviously can't be used to test how your Web application will behave on other clients (Internet Explorer/Netscape/Opera). However, that bit of testing can usually be accomplished manually (read: with your eyes!) as you reference your rendered application to aid in writing your HttpUnit test.
Additionally, since HttpUnit by default uses NekoHTML to clean up and parse the HTML in the response, it currently only supports responses that are of content type "text/html". Also, since the response's HTML has been "cleaned", it's not exactly the same as what will be presented to other clients (this can be confusing when troubleshooting a failing test!).
Lastly, while the API for traversing the response is robust, there's still occasional difficulty in navigating to that one piece of content you're interested in; verifying text within a <div> section, for example. Fortunately this limitation has a work-around, which we'll discuss now.
Creative Navigation With dom4j and XPath
As I mentioned above, there is occasionally some difficulty getting to the particular piece of content you wish to verify using HttpUnit. This can be especially painful in a Portal Web application. With so many nested tables, <div> tags, and other formatting structures, it's easy to get lost. Fortunately, HttpUnit can transform your HTML response into an XML DOM. From there, you have a myriad of XML tools at your disposal to assist you in navigating the DOM tree. I prefer dom4j, because of its tight integration with XPath. Using XPath, you can easily traverse the response, like this:
org.dom4j.Document dom4jDocument = new org.dom4j.io.DOMReader().
read(dev2devHomePage.getDOM());
org.dom4j.Element rootElement = dom4jDocument.getRootElement();
// This will return any element that contains the attribute
class="bea-portal-window" (which
// translates to all the <div> portlet elements, when using the
Portal 8.1 default skeleton).
List portletElementsList = rootElement.selectNodes
("//*[@class='bea-portal-window']");
// Iterate through the list of portlets elements to
find the one with a title of "Sample Portlet"
org.dom4j.Element samplePortletElement = null;
for(int i = 0; i < portletElementsList.size(); i++)
{
org.dom4j.Element portletElement = (org.dom4j.Element)
portletElementsList.get(i);
org.dom4j.Node titleNode = portletElement.selectSingleNode
(".//*[@class='bea-portal-window-titlebar-title']");
org.dom4j.Text textNode = (org.dom4j.Text)
titleNode.selectSingleNode(".//text()");
if(textNode.getText().equals("Sample Portlet"))
{
samplePortletElement = portletElement;
break;
}
}
|
Once you've found the portlet element you're interested in testing, you can verify its content with something like this:
org.dom4j.Element portletContentElement =
(org.dom4j.Element) samplePortletElement.selectSingleNode
(".//*[@class='bea-portal-window-content']");
// Look through all the text elements that are children of
bea-portal-window-content for our expected content.
boolean contentFound = false;
List textNodeList = portletContentElement.selectNodes(".//text()");
for(int i = 0; i < textNodeList.size(); i++)
{
org.dom4j.Text textNode = (org.dom4j.Text) textNodeList.get(i);
if(textNode.getText().indexOf("Expected Portlet Content") > -1)
{
contentFound = true;
break;
}
}
assertTrue(contentFound);
|
Using dom4j + XPath, you should be able to get to any piece of content that you're interested in testing, with a minimum amount of effort. If you're not that familiar with XPath, I recommend W3Schools for an excellent tutorial.
Executing And Automating Your Test Suite
Since HttpUnit extends JUnit, executing your new test is the same as executing any JUnit test. You can use your choice ofTestRunners, and can execute the test by itself or as part of a larger suite.If you're using Ant in your build environment, you can also use the <junit> task to invoke your tests. This target will execute your tests, and can be configured to output your test results in a variety of formats, including XML.
Since your HttpUnit tests will be making requests to a server, it's a prerequisite that your server be up and running before test execution begins. Cactus has a very handy Ant task, <runservertests>, that will assist you by:
- Starting your server, if it's not currently running
- Invoking your test target
- Stopping your server after your tests have finished
Your tests don't have to be written within Cactus in order to use <runservertests>; it works equally well with HttpUnit. See the documentation for more details.
Integration with Ant will allow easy automation of your tests: simply set up a recurring task that executes your Ant test target. You can even wrap a .bat/.sh script around the Ant invocation, and add a couple of steps to copy your results to a predefined location, for trend tracking.



