Unit Test Java Web Apps with Cactus
Extreme programming means testing often during the development life cycle, and the Cactus environment is ideal for making unit testing easier
by Kevin Jones

There has been a major movement in recent years towards Extreme Programming (XP), a trend that can be traced back at least ten years, if not more, and more recently espoused by Kent Beck and others in books such as Extreme Programming Explained (Addison-Wesley, 1999). One of the tenets of XP is "test early, test often." But why test?

Testing helps in many areas. It can show that a piece of code works; it can show that a piece of code still works after refactoring; and it helps reduce debugging time, which is often a large part of a developer's day, if not more. Often more time is spent in debugging than in writing the code! Developers love writing code, but they hate writing tests. However, for testing to be effective it has be comprehensive, which means writing lots of tests and checking the results of those tests. Both of these processes get tedious pretty quickly; however, while writing tests cannot yet be fully automated (despite the availability of tools, such as Parasoft's jTest, that provide pretty good coverage), it is possible to get the computer to check test results and display a succeeded or failed message.

Many Java developers add tests to their classes; others use a framework, the most popular being the open source JUnit testing framework available at www.junit.org. JUnit is a framework for writing unit tests.

So what is a unit test? A unit test is a highly localized test. A unit test class only tests within a single package; it doesn't test the interaction of one package with another, which is the job of functional testing.

Before writing a test case there has to be something to be tested. Recently, I found myself developing a blogging application. One of the pieces of data displayed on the Web page is a list of other sites that reference this page on any given day, with a referrer being a page that has followed a link to the site. A referrer needs two pieces of information, a hitcount and a URL. This information can be modeled in a JavaBean like this:

public class Referrer 
   implements Serializable
{
   String URL;
   int hitCount = 0;

   public String getURL()
   {
      return URL;
   }

   public void setURL(
      String URL)
   {

         this.URL = URL;
   }

   public int getHitCount()
   {
       return hitCount;
   }

   public void setHitCount(
      int hitCount)
   {
      this.hitCount = 
         hitCount;
   }
}

This is a very simple class with not much to test. In fact, if you read the JUnit FAQ there is nothing to test here. The only way this class can go wrong is if the virtual machine itself is at fault. However, the Web page is able to display up to only 35 characters for the URL, so the code needs to change to accommodate that parameter:

String displayURL;

public void setURL(String URL)
{

   this.displayURL = URL;

   this.URL = URL;

}

We've now added another String to the class (displayURL) and changed the set method. This method will eventually calculate a display value for the URL such that if the length of the URL is greater than or equal to 35 characters, it truncates the URL to 35 characters and replaces the last three characters with ellipses (…).

Test Execution
To test this class in JUnit, we need to write a test case. To write it our code has to extend junit.framework.TestCase. In your extended class add testXXX methods, and JUnit will use reflection to find all methods starting with test and execute them. Our original test case looks like this:

package com.develop.ejweb.
   beans;

public class ReferrerTest 
   extends TestCase
{
   public void 
      testReferrerInRange()
   {
      Referrer ref = 
         new Referrer();

      ref.setURL(
         "shorterthan35");
      assertEquals(
         "shorterthan35", 
         ref.getURL());

   }
}

There are a couple of points to note here. The test class is in the same package as the class being tested, and the name of the class is the same as the name of the class being tested with Test appended to it. Putting the class in the same package makes sense because often package private data and methods need to be accessed. We end the class name with Test because doing so makes it easier to create Ant build files to execute a set of tests. The test case creates an instance of the Referrer bean and then sets the value of the URL to some string that is shorter than 35 characters. The assertEquals() method is provided by JUnit, which is a check that things have worked. Here we simply check that the string we pass in is returned unchanged by the bean.

You can now execute this test. JUnit comes with three test runners: textUI, awtUI, and swingUI. To run the tests on the console simply run:

java -cp junit.jar;classes\
   main;classes\test junit.
   textui.TestRunner  \
   com.develop.ejweb.
   beans.ReferrerTest

Run it all on one line, of course. The output will be something like this:

.
Time: 0.01

OK (1 tests)

You get one '.' for each test and then a simple OK if everything succeeds. Following XP practices, we now write the tests and then write the code. The obvious first thing to test is the case where the string is longer than 35 characters. The test looks like this:

public void 
   testReferrerTooLong()
{
   Referrer ref = 
      new Referrer();

   ref.setURL(
      "urlthatisalotlonger" + 
      "than35charactersssss");
   assertEquals(
      "urlthatisalot longer" +
      "than35charact...", 
         ref.getDisplayURL());
}

This test will fail because the code doesn't yet truncate the string; therefore, you should see the result shown in Listing 1. We fix the code and rerun the tests. The fixed code looks like this:

public void setURL(String URL)
{
   if(URL == null)
   {
      this.URL = displayURL = 
         ""; 
   }

   if(URL.length() >= 35)
   {
      StringBuffer buffer = 
         new StringBuffer(
         URL);
      buffer.replace(
         32, 35, "...");
      this.displayURL = 
         buffer.substring(
         0, 35);
   }
   else
      this.displayURL = URL;

   this.URL = URL
}

We now have two tests for this piece of code, but is two enough? The answer depends on exactly what you are testing, but in this case the answer is no. When writing tests you want to test the obvious expected successes and failures, which we have done here, but you must always test boundary conditions. The boundary conditions for the previous class are when you pass an empty string, a null string, and a string that is exactly 35 characters.

A Class Fixture
Some tests require the same preconditions to be set; for example, a database connection may be necessary for each test to be run, and this connection then has to be closed at the end of each test. Enabling this JUnit allows for the creation of test fixtures. To create a fixture, add a setUp() and a tearDown() method to your test class:

protected void setUp() throws 
   Exception
{
}

protected void tearDown() 
   throws Exception
{
}

Again JUnit will use reflection to find these methods, so the signature must be exact. These methods are called before and after each test case is executed. JUnit also allows for tests to be gathered into suites; although, if you are running the tests using a build tool such as Ant, then you typically need to create individual tests.

Testing Web components such as servlets and filters is a little more complicated than the tests we have just run. Why? Because these components need access to container-managed objects such as the request, response, session, and servlet context. Because of this complexity there are two accepted ways of doing such testing: mock objects and in-container.

Testing components using mock objects means building a set of fake objects that represent each domain object needed in the test. Mock objects are good because they separate the test entirely from the container. However, mock objects can be difficult to create and manage.

In-container testing eliminates the problem of creating domain objects for each test, because the component being tested executes in a real container. However, the component being tested is not running under the exact circumstances that it will in a production environment. Some code has to be injected into the container to manage the tests, and this code may affect the container's behavior.

In-container testing is as close to integration rather than unit testing. There are advantages to both mock-object and in-container testing; however, for our purposes here we will concentrate on in-container testing.

Cactus
Cactus is part of the Jakarta project at Apache and is an open source framework for testing server-side Java components. It uses and extends the JUnit framework discussed previously. Cactus enables tests for servlets, filters, JSP tag libraries, and EJBs. A cactus test consists of both client and server-side code, and an example will make things easier to explain. Imagine a servlet like the one shown in Listing 2. This code is very simplified; it looks for a 'referer' [sic] header (the misspelling is because of a mistake in the HTTP RFC), creates a Referrer bean, populates the bean with the URL, and updates the hit count. To test this servlet we have to write a test case, deploy the servlet and the test case into a container, and make a call to the Cactus component running on that server. Cactus will then call our test case, and from there we can test the component. This test is relatively complicated when compared to the JUnit testing we did previously; however, as we will see shortly, the test case code is written in a similar way to JUnit. Before writing a Cactus test case we need to understand the cactus architecture. Cactus uses interceptors to allow it to call our test cases. Interceptors are code that runs in the server and intercepts calls that we make to a component. The interceptor will intercept our call and execute our test cases. There are three interceptors, called redirectors, one each for servlets, filters and JSPs (see Figure 1).

A Cactus test case consists of up to five methods. Three of the methods are common to a JUnit test case: setUp(), tearDown(), and testXXX. The other two methods—beginXXX and endXXX, where the XXX matches the name of the test—are there because Cactus tests execute on the client and the server. You can see the order of method calls in Figure 2. A test case would look like the one shown in Listing 3.

This class needs to be available at both the client and the server, so it needs to be copied into the Web application's WEB-INF/classes directory. On the client you execute this code with the JUnit test runner. Notice that the test case now extends ServletTestCase.

When this code is executed on the client, ServletTestCase calls any beginXXX method it finds. This method can have one of two signatures; it can either take a org.apache.cactus.WebRequest or a com.meterware.httpunit.WebRequest. Http Unit is another testing framework, and this version of the beginXXX method gives more flexibility over the HTTP you can send. The beginReferrerExists() method sets two attributes of the request. It sets the URL and it sets the value of the header in the WebRequest object of this value. The URL is not used to decide where to send the request to; it is only necessary if your servlet uses the value of the URL for internal processing. For example, if you have a controller servlet that routes requests based on the value of the URL, then it would be necessary to set this value. Any value set in the beginXXX method is copied by Cactus to the server and is used to initialize the corresponding request object on the server.

Cactus Properties
Once the beginXXX method completes, Cactus sends a request to the server. To do this, Cactus needs several pieces of information, including the URL of the application to send the request to and the names of the redirectors. This information is held in a properties file, cactus.properties, that must be on the client's classpath. The file will look something like this:

cactus.contextURL = 
   http://localhost:8080/
   cactustests
cactus.servletRedirectorName = 
   ServletRedirector
cactus.jspRedirectorName = 
   JspRedirector
cactus.filterRedirectorName = 
   FilterRedirector

These requests are sent to the appropriate redirector. These redirectors have to be configured in the application's web.xml file (see Listing 4). Once it's on the server, Cactus creates another instance of the test case. It is extremely important to remember this. There are two instances of the test case: one on the server and one on the client, which means that any user objects created on the client will not be available on the server and vice versa. Once the test case has been created on the server, Cactus will call its setUp() method. This call is the test case's chance to do any initialization that is common to all the tests, such as getting database connections. Note that setup() is called once for each test, not once for each test case. Once setup() completes, Cactus calls the testXXX method that corresponds to the beginXXX method called on the client. It is in the testXXX method that the test work is done.

The testReferrerExists() method is a fairly typical test. It creates an instance of the servlet that will be tested and calls its doGet() method. The parameters to this method's Cactus objects implement HttpServletRequest and HttpServletResponse. The request will be initialized with the parameters specified in the beginReferrerExists(), and any data set in the response will be made available on the client in a WebResponse object. The test method executes the tests; however, the test results must not be shown on the server but instead sent back to the client. To enable this result, Cactus gathers and collects all the test results and holds them internally. Once the test has executed, Cactus calls tearDown() and then returns back to the client.

At the client Cactus calls the endXXX method if there is one. In this particular case we have not provided an endReferrerExists() method as there is no response to check. The endXXX method takes a single parameter, which could be either org.apache.cactus.WebResponse or com.meterware.httpunit.WebResponse. Again, the same reasoning applies as for the request; the Http Unit WebResponse allows for greater detail to be extracted and tested for.

To try the code, create a Web application, and make sure all the Cactus libraries are in the WEB-INF/lib directory. Put the correct web.xml in the WEB-INF directory, and make sure the servlet classes and the test classes are in the WEB-INF/lib directory. On the client side make sure the test classes are on the classpath, and also make sure the cactus.properties file is on the classpath. You need to make sure any classes used in the tests are on the client classpath. You can then run the TestRunner and the tests will execute.

Extra Configuration
Notice that the servlet calls getServletContext():

ServletContext ctx = 
   getServletContext();
ctx.getRequestDispatcher(
   "someJSP");

The getServletContext() call is a wrapper around getServletConfig().getServletContext(). However, in a real container the ServletConfig is passed to the servlet through a call to init(ServletConfig config), which means that if our servlet uses ServletConfig or the ServletContext, then the servlet has to be properly initialized. Initialization is done in the test case by calling:

servlet.config(config);

where config is a Cactus implementation of the ServletConfig interface.

Testing filters is as straightforward as testing servlets, but instead of extending ServletTestCase you extend FilterTestCase. The Cactus life cycle is exactly the same: beginXXX, setup(), testXXX, teardown(), and endXXX.

You typically don't use Cactus to test JSPs, as JSPs are not pieces of logic; they should be display components. However, JSPs do use tag libraries, and Cactus can be used to test these. Tag extension testing is more difficult than testing filters and servlets because the life cycle of tag extensions is much more complicated. (Note that at the time of this writing, the new version of the JSP specification had not been released. The JSP 2.0 specification greatly simplifies the tag extension life cycle, which should also make unit testing that much easier.) To test a tag extension you extend JspTestCase. In the testXXX method you: create the tag, set the tag's PageContext, set the tag's parent (if any), set any tag attributes, call doStartTag(), call doEndTag(), and check the response in endXXX.

For iteration tags you have to call doStartTag() and then call doAfterBody() inside a loop while it returns EVAL_BODY_AGAIN. Body tags are more complicated. You need to call doStartTag(), and then create a BodyContent object to pass to the body tag. Create a BodyContent by calling pageContext.pushBody. You then call doInitBody() followed by doAfterBody(), but again you may have to call doAfterBody() inside a loop because a body tag is also an iteration tag. You finally call doEndTag() and then call popBody() to make sure you have the right body to process.

If tags are nested, then you have to create and initialize the tags in the correct order and make sure the tag's parents are properly initialized. The examples and setup discussed here are necessarily simple because I want to show the key elements of using Cactus. However, using Cactus as part of a project can be much more complicated. For example, you typically only want to maintain a single web.xml file for the project, but in the copy of the web.xml file used for production you don't want the Cactus redirectors specified.

By the same token, in the test version of Web.xml you don't want to specify any filters that are mapped to '/*' because any request to a Cactus redirector will go through the filer, the filter will do some work, a filter test case runs expecting the filter to be in a pristine state, and the test fails. You also have to make sure the URL for the application used in the cactus.properties file matches the name of the deployed application, that is, there are a number of parameters that have to be set that can change and are different depending on whether you are running the tests or not!

Real-World Testing
You also want the ability to automate your testing and have the reports generated for the tests. The reports help enable a continuous integration environment (see www.martinfowler.com/articles/continuousIntegration.html). The thrust of this discussion leads to the use of a development management tool along the lines of Ant for both managing the testing environment and to control the Cactus environment.

You also have to make sure you are using the correct version of Cactus. There are versions for the two latest releases of J2EE 1.2 and 1.3. Unit testing has become one of the backbones of application development, and JUnit makes unit testing for Java developers especially easy. When it comes to unit testing server-side components, there is a choice to be made between mock-object and in?container testing. Cactus provides an excellent way of doing in-container tests that allow for testing servlets, filters, and tag libraries.

About the Author
Kevin Jones is a developer with more than 15 years of experience. He has spent the last four years researching and teaching Java programming and most recently investigating HTTP and XML. Kevin lives in the U.K. and works for Developmentor, a training company based in the United States and Europe that specializes in technical training on Java and Microsoft platforms. Reach Kevin at .