2011/08/05 - Jakarta Cactus has been retired.

For more information, please explore the Attic.

When to use?

Your test case class should extend JspTestCase whenever you are unit testing:

  • Custom tags,
  • Any java code that uses JSP API objects (PageContext, ...)
This tutorial focuses on testing custom tags, as they are the principal code which uses the JSP API objects. Future versions of this tutorial will expand upon testing actual JSPs.

Overview of Tag Library Testing

Custom tags consist of entries in a Tag Library Descriptor file (TLD) and a tag handler class. Cactus provides the facility to test both aspects of a custom tag. However, since the TLD contains no logic, you will use Cactus primarily to test the tag handler class.

To test the tag handler class, use the implicit objects provided by JspTestCase to set up the initial state for the test. Then create and initialize your custom tag using the pageContext implicit object. After setting up the tag, call the lifecycle methods implemented by your tag in the correct order and verify that the methods return the expected results. The tag's output can be inspected in the endXXX() method.

For an additional degree of integration testing, you can create a JSP that exercises your custom tag and call it from within a regular Cactus test case. See the section on Further Integration Testing for details.

To simplify the complexities of managing a tag handlers lifecycle, Cactus provides the helper class JspTagLifecycle. It is basically a stub implementation of a JSP engines tag management routines, and provides many convenient shortcut methods to test tag handlers. For detailled documentation, check out the corresponding API documentation. Currently, JspTagLifecycle is only available for JSP 1.2.

Provided Implicit Objects

Cactus automatically initializes the following implicit objects. They are made available to your setUp(), testXXX() and tearDown() methods as instance variables of the JspTestCase class (and thus as instance variables of your test case class).

See the How it works guide for details on how Cactus initializes these objects.

The provided implicit objects are:

request

See ServletTestCase request implicit object for documentation.

response

See ServletTestCase response implicit object for documentation.

config

See ServletTestCase config implicit object for documentation.

session

See ServletTestCase session implicit object for documentation.

out

Instance variable name out
Class name public javax.servlet.jsp.JspWriter
Cactus does not wrap the out object.

You can use this object to write data to the response, thereby simulating the body of a tag (if the tag does not modify its body). If the tag does modify its body, then you will need to generate a BodyContent object before writing out the simualted body. See bodyContent for details.

pageContext

Instance variable name pageContext
Class name org.apache.cactus.server.PageContextWrapper , which inherits from javax.servlet.jsp.PageContext

Custom tags rely exclusively on the pageContext object to provide information about the enclosing JSP. Therefore this is the most important implicit object for testing custom tags. Cactus provides a very thin wrapper that ensures that all of the objects that pageContext returns (such as the ServletRequest from pageContext.getRequest()) are the correctly wrapped versions available in the other implicit variables.

See the javadoc for org.apache.cactus.server.PageContextWrapper for more details. You should also look at the samples provided in the Cactus distribution.

bodyContent

Instance variable name bodyContent
Class name javax.servlet.jsp.tagext.BodyContent

JspTestCase does not actually provide a bodyContent implicit object for use with BodyTags. However, obtaining one is so easy that it deserves mention here. Calling pageContext.pushBody() returns an object of type javax.servlet.jsp.tagext.BodyContent (which inherits from JspWriter). This call also changes the value of the "out" variable stored in page scope (and thus the value of pageContext.getOut()). To put test content into the bodyContent object, simply use its writer methods. To quote Sun's API reference on the matter: "Note that the content of BodyContent is the result of evaluation, so it will not contain actions and the like, but the result of their invocation." See Body Tags for more information.

It's important to balance calls to pushBody() with calls to popBody()--otherwise many servlet engines will not output the tag's body. The easiest way to accomplish this is to call pushBody in setUp() and popBody() in tearDown().

Custom Tag Set Up

Creating the test fixture for a custom tag test involves several steps. The exact order of the steps can vary depending on the needs of the test. For instance, placing the test data in the correct scope would probably happen before a real JSP began its execution. You can emulate this, or choose to do it after the tag has been in initialized (as described below). In most cases you can determine the exact order of the steps based on what is most convenient for a given test (some steps may be specific to only one test in the TestCase and so should be executed after common setUp() code).

Step 1: Create the Tag (Required)

Instantiate a copy of the tag you wish to test.

SomeTag tag = new SomeTag();

Step 2: Set the pageContext (Optional)

Call the setPageContext() method with the implicit object provided by Cactus to register the pageContext with the tag.

tag.setPageContext(pageContext);

Step 3: Set the tag's attributes (Optional)

If your tag takes attributes, call setter methods to initialize the tag's state. Setters on the tag handler class represent the attributes of custom tags. Thus to emulate this JSP fragment:

<someTag foo="10" bar="11"/>

You would need to use the following:

someTag.setFoo("10");
someTag.setBar("11");

Step 4: Set the parent tag (Optional)

If you would like the tag you are testing to access a parent tag, you will need to call

tag.setParent(enclosingTag);

This will allow tag to successfully call getParent and TagSupport.findAncestorWithClass(). Of course enclosingTag will have to be instantiated and set up as well, including another call to setParent() if you would like to simulate multiple levels of nesting.

Step 5: Create the BodyContent Object (Optional)

If your tag processes its body, call pageContext.pushBody() to obtain a BodyContent. If you employ this step, be sure to also include a call to pageContext.popBody() after the tag finishes execution. See the Body Tags section for more details.

Step 6: Set up page state (Optional)

Set up any necessary page state by putting test objects into the appropriate scopes. Tags frequently access data in the session, the request, or the page. If your tag operates on data contained in any of these (or in the application scope), be sure to set up this part of the test fixture. Objects can be placed in these scopes by using the implicit objects provided by Cactus directly, or by accessing them indirectly through the pageContext object.

request.setAttribute("key", new DomainObject("testValue"));
//or
pageContext.setAttribute("key", new DomainObject("testValue"), PageContext.REQUEST_SCOPE);

Running the Test

Once the tag has been set up and any necessary page data has been placed in the appropriate scopes, testing a custom tag consists of calling the relevant life-cycle methods and then using JUnit asserts to verify the outcome.

Verifying individual methods

Most of the life cycle methods return ints, which signal that the container should take a certain action after the method. For instance, the constant EVAL_BODY_INCLUDE returned from doStartTag() tells the container to include the tag's body in the JSP's output response. So a tag which conditionally includes its body based on the value of one of its attributes might be verified like this:

tag.setValueThatResultsInInclude("correct value");
assertEquals(Tag.EVAL_BODY_INCLUDE, tag.doStartTag());

Checking effects on page data

In addition to "listening" for the signals that your tag sends to the container, you may want to verify that the tag's execution has the appropriate effects upon the page data. Use JspTestCase's implicit objects to verify that the tag has correctly modified the information. The following snippet verifies that the CatalogListTag has placed a collection of objects in the request under the key "catalogs":

catalogListTag.doStartTag();
Collection catalogs = (Collection)request.getAttribute("catalogs");
assertNotNull(catalogs);

Verifying tag output

Use the endXXX method to verify that your tag's methods have resulted in the correct data being written to the response.

This example uses the endXXX() signature from Cactus 1.2 or above.
public void endSomeTagTest (WebResponse response)
{
    String output = response.getText();
    assertEquals("<b>expected output</b>", output);
}

Special Cases

There are a few scenarios in custom tag testing that deserve extra attention.

Iteration Tags

To test a tag that repeats its body processing a number of times, simply create a do-while loop that mimics the life cycle of an iteration tag:

//[...tag set up and early life cycle methods omitted...]

int count = 0;
do
{
    count++;

} while (tag.doAfterBody() == tag.EVAL_BODY_AGAIN);

tag.doEndTag();

//based on setUp we expect 9 repetitions
assertEquals(9, count);

You can use a count variable (such as the one illustrated in the example) to check whether the tag's body was processed the expected number of times.

Body Tags

Unless specified otherwise by the deployment descriptor, all tags can include a body, which can in turn include other tags or scriptlet expressions. These are automatically evaluated at run time, and the content of the body is simply written out if the tag signals it should be (with EVAL_BODY_INCLUDE for instance). Nothing special is required to test this sort of tag, since the tag is unconcerned about its contents.

Testing BodyTags--tags which actually perform some processing on their content--is a little trickier. BodyTags can choose to return a constant ( EVAL_BODY_TAG in JSP 1.1, EVAL_BODY_BUFFERED in 1.2) from doStartTag() which signals to the container that the tag would like a chance to handle its own body. If it receives this result, the container calls pageContext.pushBody() to obtain a BodyContentobject. The BodyContent object is passed to the tag through the tag's setBodyContent()method. The container then uses this object (the old out object is saved) to capture all of the response writing that goes on in the body of the tag. After the tag's body has been evaluated, the tag itself has a chance to do something with the result of the evaluation in its doAfterBody() method. After the tag has completed its execution, the container restores the old out object with a call to pageContext.popBody().

To test body tags, your test must replicate this somewhat complicated lifecycle. The following code covers all of the steps as they might appear in a typical test:

//standard set up
YourTag tag = new YourTag();
tag.setPageContext(this.pageContext);
tag.doStartTag();

//obtain the bodyContent object--presumably doStartTag has returned 
//EVAL_BODY_TAG or EVAL_BODY_BUFFERED.
BodyContent bodyContent = this.pageContext.pushBody();
this.tag.setBodyContent(bodyContent);
this.tag.doInitBody();


//write some "output" into the bodyContent so that endXXX can test for it.
bodyContent.println("Some content");
bodyContent.print("Some evaluated content " + (5 + 9));

//actually handles the processing of the body
tag.doAfterBody();

//after the body processing completes
tag.doEndTag();

//finally call popBody
this.pageContext.popBody();

This sample does not fully replicate the container's handling of the tag (for instance, the tag would only receive the bodyContent object if the result of doStartTag indicated that it should do so). However, in a test environment, you can make assumptions if doing so simplifies the workings of the test.

Again, you can check that the body of the tag was handled correctly by verifying the total output in the endXXX() method.

TagExtraInfo classes

Cactus does not offer any specific services to support the testing of TagExtraInfo classes because they do not depend on any of the implicit objects.

Further Integration Testing

You can use Cactus to test how your tag will react when put into a real JSP. This allows you to verify that there are no problems with the deployment descriptor, or unexpected behavior on the part of the container. You accomplish this by writing a small JSP that makes use of your custom tag, and then calling it from within a Cactus test case. You can even use JUnit assertions within scriptlets to verify certain aspects of the Tag's behavior. However, this method requires that you write a separate JSP for each test case (or lump several cases into a single JSP). Both options pose problems, so it may be best to include one or two tests of this type and rely on the more traditional methods described earlier to ensure total coverage.

The test JSP

All the JSP needs to do is include the tag library that describes the tag you are testing and makes use of it in some way. You can import junit.framework.Assert to do some simple checks on the effects of the tag. Here is a short example of a JSP that exercises a tag:

<%@page import="junit.framework.Assert"%>
<%@taglib uri="WEB-INF/yourTagLib.tld" prefix="example"%>

Here is the custom tag this page verifies:
<example:someTag variableName="foo" variableValue="bar"/>

Here is the JUnit assert that checks whether the tag correctly created a 
scripting variable named <code>foo</code> with the value "bar":
<%
//attempt to reference foo will cause a translation error if the tag did not 
//create the scripting variable
Assert.assertEquals("bar", foo);
%>

It's a bad idea to put too many assertions into the JSP. In the example above, the creation of a scripting variable can only be tested within the JSP page. (The same goes for any objects in page scope, because each JSP creates its own.) If you want to use other assertions with this type of test, call them in your test case after pageContext.include() (See below for an example.)

The TestCase

To use the test JSP, include it from within a JspTestCase. The convenience function pageContext.include() takes care of this nicely:

public void testSomeTag () throws Exception
{
    pageContext.include("/test_some_tag.jsp");

    //an assert to check whether the page also mapped foo into the session
    assert("bar", session.getAttribute("foo"));
}

Exceptions that result from either page translation (such as required attributes being omitted, or the tag missing a part of its descriptor entry) or page execution (such as the tag being unable to find required data in the appropriate scope) are automatically be thrown up to this level. If you do not catch them there they will be logged by Cactus/JUnit as failures--which is just what you want.

Any output that the test JSP generates can be checked normally in the endXXX method.

Of course, using this strategy means that you need to put the test_some_tag.jsp in the specified location within your web application. If you are using JSP test case your build script should already deploy the redirector JSP, so it should be easy to include another JSP in the build process.