7.6 Writing a Cactus Test

7.6.1 Problem

You want to use Cactus to test server-side code.

7.6.2 Solution

Extend the appropriate Cactus test-case class and implement one or more testXXX( ), beginXXX( ), and endXXX( ) methods.

7.6.3 Discussion

Cactus is a testing framework that extends JUnit to provide a way to execute tests against code running in a server. Specifically, Cactus allows for testing servlets, JSPs, and filters while running within a servlet container.

There are seven main steps to writing a Cactus test.

  1. Import the JUnit and Cactus classes:

    import org.apache.cactus.*;
    import junit.framework.*;
  2. Extend one of three Cactus test case classes:

    org.apache.cactus.ServletTestCase

    Extend this class when you want to write unit tests for your servlets. For example, if you need to test how a servlet handles HttpServletRequest, HttpServletResponse, HttpSession, ServletContext, or ServletConfig objects, write a ServletTestCase:

    public class TestMyServlet extends ServletTestCase {
    }
    org.apache.cactus.JspTestCase

    Extend this class when you want to write unit tests for your JSPs. For example, if you need to test a custom tag library or JspWriter, write a JspTestCase:

    public class TestMyJsp extends JspTestCase {
    }
    org.apache.cactus.FilterTestCase

    Extend this class when you want to write unit tests for your filters. For example, if you need to test that a FilterChain or FilterConfig object executes correctly, write a FilterTestCase:

    public class TestMyFilter extends FilterTestCase {
    }
  3. Implement the JUnit setUp( ) and tearDown( ) methods. The setUp( ) and tearDown( ) methods are optional methods that can be overridden by your test. Unlike a normal JUnit test that executes these methods on the client, Cactus executes these methods on the server. This allows you access to the implicit objects defined by Cactus. Here is an example of setting an attribute on the HttpSession implicit object:

    public void setUp(  ) throws Exception {
        this.session.setAttribute("BookQuantity", new Integer(45));
    }
    
    public void tearDown(  ) throws Exception {
        this.session.removeAttribute("BookQuantity");
    }

    As in JUnit, the setUp( ) and tearDown( ) methods are executed for each testXXX( ) method. The only twist is that Cactus executes these methods on the server.

Using the Constructor in Place of setUp( ) May Cause Problems

An idiom for writing JUnit tests is to use the constructor instead of overriding the setUp( ) method. This works because each testXXX( ) method creates a new instantiation of the test class, so instance variables are allocated for each test. Here's an example of using the setUp( ) method:

public void setUp(  ) {
    this.myobject = new MyObject(  );
}

Here's how the same thing can be accomplished using the constructor:

public MyJUnitTest(String name) {
    super(name);
    this.myobject = new MyObject(  );
}

This technique may cause problems with Cactus if you try to set up information on any of the implicit objects. The implicit objects are only valid on the server and are not initialized until after the constructor is executed. The following code causes a NullPointerException:

public MyCactusTest(String name) {
    super(name);
    // the implicit HttpSession is null
    this.session.setAttribute("name","value");
}

Here's the correct solution:

public void setUp(  ) {
    // the implicit HttpSession is valid
    this.session.setAttribute("name","value");
}

Be careful when using JUnit idioms because extensions of the JUnit framework may behave quite differently than expected.

  1. Implement the testXXX( ) methods. All Cactus test methods are defined exactly the same as JUnit test methods. The only difference is that Cactus test methods are executed on the server. In the testXXX( ) method you:

    • Instantiate and optionally initialize the servlet to test.

    • Call the method to test.

    • Use the standard JUnit assertion methods to verify the expected results.

    Here is an example:

    public void testMyTest(  ) throws Exception {
        MyServlet servlet = new MyServlet(  );
        assertTrue(servlet.doSomethingThatEvaluatesToTrueOrFalse(  ));
    }

    Notice the explicit instantiation of the servlet. This is something that servlet developers never have to do in a real web application because the servlet container is responsible for the servlet's lifecycle. In the Cactus world, you must take on the role of the servlet container and ensure that the servlet is instantiated, and if needed, initialized by invoking init(ServletConfig) method.

  2. Optionally, implement the beginXXX(WebRequest) method. For each testXXX( ) method, you may define a corresponding beginXXX( ) method. This method is optional and executes on the client before the testXXX( ) method. Since this method executes on the client, the implicit objects we had access to in the setUp( ), tearDown( ), and testXXX( ) are null.

  3. Here is the full signature:

    public void beginXXX(org.apache.cactus.WebRequest) {
        // insert client side setup code here
    }

    This method can be used to initialize HTTP related information, such as HTTP parameters, cookies, and HTTP headers. The values set here become available in the testXXX( ) method through the appropriate implicit object.

  4. Optionally, implement the endXXX(WebResponse) method. For each testXXX( ) method you may define a corresponding endXXX( ) method. This method is optional and executes on the client after the testXXX( ) method successfully completes. If the testXXX( ) method throws an exception this method is not executed.

    Cactus provides support for two endXXX(WebResponse) signatures. Here are the valid signatures:

    public void endXXX(org.apache.cactus.WebResponse) {
        // insert client side assertions
    }
    
    public void endXXX(com.meterware.httpunit.WebResponse) {
        // insert client side assertions
    }

    Use the first signature if you want to use the WebResponse that comes with the Cactus framework. This object provides a very simple view of the response. Use the second signature if you want to use the WebResponse object that is distributed with HttpUnit. This object provides a detailed view of the response, providing a much richer API. The Cactus framework accepts either signature in your test with absolutely no effort on your part.

  5. Finally, compile, deploy the web application to the server, and execute the tests.

7.6.4 See Also

Recipe 7.3 describes how to set up a stable build environment for server-side testing. For more information on using HttpUnit to perform complex assertions in the endXXX(WebResponse) method, see Chapter 5.