Using HttpUnit for Black Box Testing


Andy Meneely, Lauren Hayward, Sarah Heckman, and Laurie Williams [Contact Authors]
CSC 326 - Software Engineering
Department of Computer Science
North Carolina State University

Back to Software Engineering Tutorials


0.0 Contents
1.0 What is HttpUnit?
2.0 Installing HttpUnit in iTrust
3.0 Using HttpUnit
4.0 Automating a Black Box Test Case
5.0 Some Handy HttpUnit Tests
6.0 Exercise
7.0 Resources

1.0 What is HttpUnit?

HttpUnit is a Java API for interacting with websites. Acting as a browser, HttpUnit enables a programmer to automate testing on a website by sending HTTP requests, returning the responses, and parsing the HTML that is returned. HttpUnit also supports HTTP headers, cookies, Javascript execution, DOM, and many other features used in web development. Although HttpUnit is typically used as a utility for JUnit tests (hence its name), it can actually be used for any automated interaction over HTTP - test or not.

For our purposes, we will be using HttpUnit for black box testing of our J2EE web application, CoffeeMaker. HttpUnit allows us to test navigating through our JSPs by clicking links and buttons and filling out forms. In this tutorial, we will explain AddRecipeHTTPTest which contains sample tests cases for the AddRecipe user story in the CoffeeMaker example.

Download CoffeeMaker_WebTest.zip and import into Eclipse to follow along with the tutorial.

Top | Contents

2.0 Installing HttpUnit in iTrust

HttpUnit consists of a number of Java jars files used for various purposes. These jars are located in the lib/ directory of CoffeeMaker. If you want to use HttpUnit elsewhere, you can download the HttpUnit 1.6 and put libraries on your classpath.

NOTE: Do not use HttpUnit 1.7. This version currently submits the request twice when submitting a form.

Top | Contents

3.0 Using HttpUnit

HttpUnit uses the following key classes:

  • WebConversation represents the browser. With WebConversation, you can make requests, store cookies, store frames, change general settings, etc. For CoffeeMaker_WebTest, it's important to keep track of the same WebConversationin a test as it stores your session authentication info. If you want to simulate multiple users on the system, use multiple WebConversations.
  • WebResponse represents the actual response from the website. Another way of thinking of the WebResponse is the actual page itself. The WebResponse class has accessors such as getText() which will return the actual HTML, and getLink(), getTitle(), etc will parse the HTML.
  • HttpException represents an HTTP error (such as 404 - Not Found, or 403 - No Access). Be sure to use the com.meterware.httpunit.HttpException and not the other one!
  • WebRequest is an abstract class representing a request. The PostMethodWebRequest represents a form posting (which is used throughout iTrust).

A typical HttpUnit session might look like the the following code. Can you see what it does?

public static final String ADDRESS = "http://localhost:8080/CoffeeMaker_WebTest";

public void testMenuPage() {
try {
WebConversation webConversation = new WebConversation();
WebResponse menuResponse = webConversation.getResponse(ADDRESS);
assertEquals("CoffeeMaker", menuResponse.getTitle());
} catch (SAXException e) {
fail("SAXException: " + e.getMessage());
} catch (IOException e) {
fail("IOException: " + e.getMessage());
}
}

The code requests the CoffeeMaker menu, and then checks that the title of the returned page is CoffeeMaker.

Next, we're going to test the AddRecipe page. First, we will select the "Add a Recipe" link on the CoffeeMaker menu page, and then we will submit the form with some test values. The code below shows how this is done.

public void testAddRecipe1() throws Exception {
//Go to add_recipe.jsp
WebConversation webConversation = new WebConversation();
WebResponse menuResponse = webConversation.getResponse(ADDRESS);
WebResponse addRecipePage = menuResponse.getLinkWith("Add a recipe").click();

//Fill out form and submit
addRecipePage.getForms()[0].setParameter("name", "Coffee");
addRecipePage.getForms()[0].setParameter("price", "50");
addRecipePage.getForms()[0].setParameter("amtCoffee", "3");
addRecipePage.getForms()[0].setParameter("amtMilk", "1");
addRecipePage.getForms()[0].setParameter("amtSugar", "1");
addRecipePage.getForms()[0].setParameter("amtChocolate", "0");
addRecipePage = addRecipePage.getForms()[0].submit();

//Test that Recipe added
assertTrue(addRecipePage.getText().contains("Coffee successfully added."));

//Go to main menu
menuResponse = addRecipePage.getLinkWith("Back to CoffeeMaker Menu").click();
}

A few comments worthy of note:

  • HttpUnit tries to simulate the user experience by allowing you to chain commands in a single line of code - as if you're clicking through the website. As a result, however, not all of the details of HTML browsing are supported. Of notable absence is the ability to manipulate hidden fields. The HttpUnit website's FAQ provides a few tricks to get around this, and when all else fails, you can always encode your hidden fields in the URL (e.g. mypage.jsp?formIsFilled=true)
  • The WebResponse has many different ways of finding the element you're looking for. Most methods that ask for a string will do a substring match (such as the getLinkWith() method)

Before running HttpUnit tests, add your project to your Tomcat server and start the server. To run all of the HttpUnit tests, right-click on the httptests/ folder, select Run As > JUnit Test. See the lab deployment instructions or home deployment instructions on the iTrust website for more information about how to deploy a web project like iTrust (for the tutorial you'll want to deploy CoffeeMaker_WebTest).

Top | Contents

4.0 Automating a Black Box Test Case

Let’s begin by examining the following black box test case for the user story AddRecipe.

Test ID Description Expected Results Actual Results
addRecipe1*

Precondition: Run CoffeeMaker
Enter: Menu option 1, "Add a recipe."
Name: Coffee
Price: 50
Coffee: 3
Milk: 1
Sugar: 1
Chocolate: 0
Return to main menu.

Coffee successfully added.  
addRecipe2

Precondition: Run CoffeeMaker, addRecipe1 test.
Enter: Menu option 1, "Add a recipe."
Name: Coffee
Price: 50
Coffee: 3
Milk: 1
Sugar: 1
Chocolate: 0

Coffee could not be added.  

The first test case, addRecipe1, is implemented above. Next, we want to implement addRecipe2. Since HttpUnit tests are typically organized by operation, we'll want to put addRecipe2 in the AddRecipeHTTPTest.java file.

Now, lets write our test! We want our automated test to directly relate to our manual test, so we will give our automated test the same name and declare it inside of AddRecipeHTTPTest.java:

public void testAddRecipe2() throws Exception {

}

First, we want to start CoffeeMaker and then select the menu item "Add a recipe."

WebConversation webConversation = new WebConversation();
WebResponse menuResponse = webConversation.getResponse(ADDRESS);
WebResponse addRecipePage = menuResponse.getLinkWith("Add a recipe").click();

The next step is to fill out the form. Each form object on an web page is found in the array returned by the WebResponse.getForms() method. There is only one form object in addRecipe.jsp; therefore, the form is at position 0 in the forms array.

For each object in the form we want to enter the value that the user would input for that object. This is done with the setParameter() method. The first argument to setParameter() is the name of the form object and the second argument is the value. The code below adds a Coffee recipe with the specified values. Remember that the precondition for addRecipe2 test is that addRecipe1 test has already run. However, with HttpUnit, each test method is run independent of the other test methods; therefore, we must copy the addRecipe1 test code into the testAddRecipe2() method and then write the rest of the addRecipe2 test.

addRecipePage.getForms()[0].setParameter("name", "Coffee");
addRecipePage.getForms()[0].setParameter("price", "50");
addRecipePage.getForms()[0].setParameter("amtCoffee", "3");
addRecipePage.getForms()[0].setParameter("amtMilk", "1");
addRecipePage.getForms()[0].setParameter("amtSugar", "1");
addRecipePage.getForms()[0].setParameter("amtChocolate", "0");

After entering all of the form information, we now want to submit the form. This is done with the WebResponse.submit() method. The submit() method returns a WebResponse object of the new page that is loaded after the submit is complete. In this case, the form in add_recipe.jsp submits to add_recipe.jsp.

addRecipePage = addRecipePage.getForms()[0].submit();

In addRecipe1, the next line was a test to ensure that the coffee recipe was added successfully. We don't need to retest this. However, we do need to finish the addRecipe1 precondition by returning to the main menu.

menuResponse = addRecipePage.getLinkWith("Back to CoffeeMaker Menu").click();

Now, we can start on the rest of addRecipe2. Again, we will click the "Add a recipe." link to go to the add_recipe.jsp page. This time, we don't require as much code because the WebConversation has already been created, and we're currently at the main menu.

addRecipePage = menuResponse.getLinkWith("Add a recipe").click();

Again, we will fill out the form with the specified information and submit the form.

addRecipePage.getForms()[0].setParameter("name", "Coffee");
addRecipePage.getForms()[0].setParameter("price", "50");
addRecipePage.getForms()[0].setParameter("amtCoffee", "3");
addRecipePage.getForms()[0].setParameter("amtMilk", "1");
addRecipePage.getForms()[0].setParameter("amtSugar", "1");
addRecipePage.getForms()[0].setParameter("amtChocolate", "0");
addRecipePage = addRecipePage.getForms()[0].submit();

Now we can test that the recipe was not added (the AddRecipe user story states that there can only be one recipe with a given name in the CoffeeMaker).

assertTrue(addRecipePage.getText().contains("Coffee could not be added."));

The final testAddRecipe2() method looks like:

public void testAddRecipe2() throws Exception {
   //Go to add_recipe.jsp
   WebConversation webConversation = new WebConversation();
   WebResponse menuResponse = webConversation.getResponse(ADDRESS);
   WebResponse addRecipePage = menuResponse.getLinkWith("Add a recipe").click();

   //Fill out form and submit
   addRecipePage.getForms()[0].setParameter("name", "Coffee");
   addRecipePage.getForms()[0].setParameter("price", "50");
   addRecipePage.getForms()[0].setParameter("amtCoffee", "3");
   addRecipePage.getForms()[0].setParameter("amtMilk", "1");
   addRecipePage.getForms()[0].setParameter("amtSugar", "1");
   addRecipePage.getForms()[0].setParameter("amtChocolate", "0");
   addRecipePage = addRecipePage.getForms()[0].submit();

   //Test that Recipe added
   assertTrue(addRecipePage.getText().contains("Coffee successfully added."));

   //Go to main menu
   menuResponse = addRecipePage.getLinkWith("Back to CoffeeMaker Menu").click();

   //Go to add_recipe.jsp
   addRecipePage = menuResponse.getLinkWith("Add a recipe").click();

   //Fill out form and submit
   addRecipePage.getForms()[0].setParameter("name", "Coffee");
   addRecipePage.getForms()[0].setParameter("price", "50");
   addRecipePage.getForms()[0].setParameter("amtCoffee", "3");
   addRecipePage.getForms()[0].setParameter("amtMilk", "1");
   addRecipePage.getForms()[0].setParameter("amtSugar", "1");
   addRecipePage.getForms()[0].setParameter("amtChocolate", "0");
   addRecipePage = addRecipePage.getForms()[0].submit();

   //Test that Recipe not added
   assertTrue(addRecipePage.getText().contains("Coffee could not be added."));
}

Run our test as a JUnit test. Green bar!

Top | Contents

5.0 Some Handy HttpUnit Tests

You now have all of the tools you need to execute JUnit test via making HTTP requests using HttpUnit. Below are some other helpful HttpUnit code snippets that might help for security testing. They are not meant to be complete, so please adapt to taste.

The following helps for expecting an authorization (403) error.

try {
	new WebConversation().getResponse("URL YOU SHOULDN'T GET TO!");
	fail("exception should have been thrown");
} catch (HttpException e) {
	assertEquals(403, e.getResponseCode());
}

HttpUnit also parses tables - however, it handles nested tables rather peculiarly. I recommend using getTableStartingWithPrefix which searches through tables and will match the first non-blank cell. Once you obtain the table, you can pull cells individually using zero-based row and column indices.

WebConversation webConversation = new WebConversation();
WebResponse loginResponse = webConversation
	.getResponse("http://localhost:8080/iTrust/auth/hcp-uap/editBasicHealth.jsp?pid=2");
WebResponse basicHealth = loginResponse.getLinkWith("HCP").click();
WebTable table = basicHealth.getTableStartingWithPrefix("Basic Health");
assertEquals("Smokes?", table.getCellAsText(1, 2));

HttpUnit does not currently support iFrames. iTrust uses an iFrame to select users (for example, when editing a patient, the user enters the patient mid to select the user). The iFrame sets the hidden field on the page that accepts the mid. Since HttpUnit does not support the use of iFrames, our tests manually set the hidden field bypassing the iFrame (as shown below). When the HttpUnit API expands to support iFrames, we will add tests to specifically test the iFrame.

WebForm patientForm = wr.getForms()[0];
patientForm.getScriptableObject().setParameterValue("UID_PATIENTID", "2");
patientForm.getButtons()[1].click();

It is often much easier to read the HttpUnit tests when the html makes use of name or id field. For example, the html form in addHCP.jsp does not make use of the id tag:

<form action="addHCP.jsp" method="post">

Therefore, in order to select the form inside our HttpUnit test, we must use

form = wr.getForms()[0];

We can add the id field to the form tag:

<form action="addHCP.jsp" method="post" id="addHCP">

Our HttpUnit test could then select the form by calling

form = wr.getFormWithID("addHCP");

This makes our test much more readable and clear. As you write tests, you may find it beneficial to edit the HTML to make the jsp pages easier to be tested.

Top | Contents

6.0 Exercise

You will implement the AddInventory user story, which will use the database for maintaining inventory and automate the testing of the addInventory1 acceptance test.

The main steps for completion are:

  • Download CoffeeMaker_WebTest.zip and import into Eclipse if you haven't done so already.
  • Copy the add_inventory.jsp file you completed as part of the JSP tutorial. If you have not completed the JSP tutorial, you will want to complete add_inventory.jsp so you can test your solution.
  • The CoffeeMaker Black Box Test cases are online. Implement addInventory1 black box test, which is provided for you below, in a class called AddInventoryHTTPTest.java. The test class should be stored in the httpunit source folder and belong to the package edu.ncsu.csc.coffeemaker.http.
  • Note that your HttpUnit test should set up the proper test data before each run, and that your test should green-bar, along with all other unit tests in the system.
  • You do not need to Javadoc your new test class (but all other classes should be Javadoced).
Test ID Description Expected Results Actual Results
addInventory1*

Precondition: Run CoffeeMaker
Enter: Menu option 4, "Add inventory"
Coffee: 5
Milk: 3
Sugar: 7
Chocolate: 2
Return to main menu.

Inventory successfully added

 

Refer to the assignment instructions for details on submission.

Top | Contents

7.0 Resources
HttpUnit
CoffeeMaker
Top | Contents

Back to Software Engineering Tutorials
Using HttpUnit for Black Box Testing Tutorial © 2006-2008 North Carolina State University, Laurie Williams, Andy Meneely, Lauren Hayward,
and Sarah Heckman
Email the authors with any questions or comments about this tutorial.
Last Updated: Tuesday, August 27, 2013 8:19 PM