|
Last update : April 21 2002
Doc for : v1.3
About
What is Cactus ?
News
Changes
Features/Status
Goals
Roadmap/Todo
Contributors
Contributing
Cactus Users
Tested on ...
License
Downloads
Downloads
Documentation
How it works ?
Getting Started
Mock vs Container
Javadocs
FAQ
Howto Guides
Classpath Howto
Config Howto
Migration Howto
TestCase Howto
Security Howto
Ant Howto
HttpUnit Howto
Sample Howto
EJB Howto
IDE Howto
JUnitEE Howto
Support
Bug database
Mailing list
Misc.
Why the name ?
Logo Challenge
Resources
Stats
Developers
CVS
Coding Conventions
Build results
|
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.
|
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
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 it's
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.
|
|
|
|