Your test case class should extend JspTestCase
whenever you are unit testing:
PageContext
, ...)
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.
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.
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).
The provided implicit objects are:
See ServletTestCase
request
implicit object for documentation.
See ServletTestCase
response
implicit object for documentation.
See ServletTestCase
config
implicit object for documentation.
See ServletTestCase
session
implicit object for documentation.
Instance variable name |
out
|
Class name |
public javax.servlet.jsp.JspWriter
|
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.
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.
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.
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()
.
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).
Instantiate a copy of the tag you wish to test.
SomeTag tag = new SomeTag();
Call the setPageContext()
method with the implicit
object provided by Cactus to register the pageContext with the
tag.
tag.setPageContext(pageContext);
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");
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.
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.
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);
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.
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());
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);
Use the endXXX
method to verify that your tag's
methods have resulted in the correct data being written
to the response.
endXXX()
signature from Cactus 1.2 or above.
public void endSomeTagTest (WebResponse response) { String output = response.getText(); assertEquals("<b>expected output</b>", output); }
There are a few scenarios in custom tag testing that deserve extra attention.
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.
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
BodyContent
object. 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.
endXXX()
method.
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.
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.
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.)
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.
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.