Hi all,
[I'm cc-ing to taglibs-dev because I assume this is of interest there too.]
As some of you might have noticed, I've recently submitted a new
extension class to Cactus that is supposed to help with the testing of
JSP tag libraries.
In essence, the purpose is to free the developer from knowing all the
ins and outs of the JSP tag extension API, and especially how the
container and JSP compiler handles the tags. So instead of calling all
the lifecycle methods (setPageContext, setParent, doStartTag,
doAfterBody, etc, etc) directly - which is quite error-prone -, you
delegate that to the JspTagLifecycle class. That class still gives you
the power to verify just about every aspect of the tag under test, by
providing points where the lifecycle can be intercepted by the test.
Here are a couple of examples copied directly from the Cactus
Sample-Servlet test suite, which uses JspTagLifecycle to test the
JSTL-core tags:
Example 1 (with body content)
-----------------------------
/**
* Tests the <code><c:out></code>-tag with a proper, literal
* value for it's <code>value</code> attribute that contains special
* XML characters that need to be escaped.
*/
public void testOutTagEscapeXml()
throws JspException, IOException {
OutTag tag = new OutTag();
tag.setEscapeXml("true");
JspTagLifecycle lifecycle = new JspTagLifecycle(pageContext, tag);
tag.setValue("<value/>");
lifecycle.invoke();
}
public void endOutTagEscapeXml(WebResponse theResponse) {
String output = theResponse.getText();
assertEquals("<value/>", output);
}
Example 2 (conditional)
-----------------------
/**
* Tests the conditional tag <c:if> by providing a proper,
* literal value to it's <code>test</code> attribute that evaluates
* to the boolean value <code>true</code>. The test verifies the
* correct behaviour by asserting that the tag's body is not
* skipped.
*/
public void testIfTagTrue()
throws JspException, IOException {
IfTag tag = new IfTag();
JspTagLifecycle lifecycle = new JspTagLifecycle(pageContext, tag);
tag.setTest("true");
lifecycle.invoke(new JspTagLifecycle.Interceptor() {
public void skipBody() {
fail("Body should have been evaluated!");
}
});
}
Example 3 (iteration)
---------------------
/**
* Tests the tag <c:forEach> by providing a comma-delimited list
* of string to it's <code>items</code> attributes, and checking the
* exposed scoped variable on every iteration step.
*/
public void testForEachTag()
throws JspException, IOException {
ForEachTag tag = new ForEachTag();
JspTagLifecycle lifecycle = new JspTagLifecycle(pageContext, tag);
tag.setVar("Item");
tag.setItems("One,Two");
lifecycle.invoke(new JspTagLifecycle.Interceptor() {
public void evalBody(int iteration, BodyContent body) {
String item = (String)pageContext.findAttribute("Item");
assertNotNull(item);
if (iteration == 0) {
assertEquals("One", item);
} else if (iteration == 1) {
assertEquals("Two", item);
} else if (iteration == 2) {
assertEquals("Three", item);
} else {
fail("More iterations than expected!");
}
}
});
}
Now, while this API let's you test just about every aspect of the
tag-under-test, I'm not quite sure whether it's really friendly,
especially when it comes to nested/collaborative tags. I'm playing with
a couple of ideas, and I'd very much appreciate feedback if you have any
good ideas or general concerns.
First, I've been thinking about adding shortcut methods to verify that
the body has been evaluated or skipped, so that [Example 2] could be
written as follows:
IfTag tag = new IfTag();
JspTagLifecycle lifecycle = new JspTagLifecycle(pageContext, tag);
tag.setTest("true");
lifecycle.invoke();
lifecycle.assertBodyEvaluated();
Similar shortcut methods like "assertBodyEvaluated(int numberOfTimes)"
are imaginable, but I can't see an easy way around the Interceptor usage
when you need to do assertions about the pageContext "inside the body",
as in [Example 3]. So the idea would be to allow chaining of
interceptors (currently only one is allowed), and have the shortcut
methods implicitly insert specialized interceptors into the chain. Test
case specific interceptors defined as anonymous inner classes would
still be allowed for advanced tests.
[I'm a bit uncertain about the "assertXXX" naming scheme, maybe
"expectXXX" would be better?]
Second, the testing of tag collaboration is pretty hard to accomplish
using the current strategy. Ideally, I'd like to allow nesting of
JspTagLifecycle instances, to be able to write tests like this:
ChooseTag chooseTag = new ChooseTag();
JspTagLifecycle chooseLifecycle =
new JspTagLifecycle(pageContext, chooseTag);
WhenTag whenTag = new WhenTag();
JspTagLifecycle whenLifecycle =
new JspTagLifecycle(pageContext, whenTag);
whenTag.setTest("false");
whenLifecycle.assertBodySkipped();
OtherwiseTag otherwiseTag = new OtherwiseTag();
JspTagLifecycle otherwiseLifecycle =
new JspTagLifecycle(pageContext, otherwiseTag);
otherwiseLifecycle.assertBodyEvaluated();
chooseLifecycle.addNestedTag(whenLifecycle);
chooseLifecycle.addNestedTag(otherwiseLifecycle);
chooseLifecycle.invoke();
This looks a bit complicated, but actually isn't ;-) ... Basically, when
a JspTagLifecycle has nested tags, their lifecycle will be invoked when
the body of the enclosing tag is evaluated. There could also be an
addNestedText(String) method, that would add simple template text to the
body content of the enclosing tag. All nested elements would be invoked
in the order they have been added.
-----
So these are the thoughts I'm playing with. I'd love to get some
feedback: Is this something that would simplify taglib testing for you?
Any recommandations for the API? Concerns about the general approach? etc.
[BTW, I'm still waiting for karma to commit a couple of fixes to the
version in Cactus CVS, so if you want to check it out, be warned that
there are a couple of problems]
--
Christopher Lenz
/=/ cmlenz at gmx.de
--
To unsubscribe, e-mail: <mailto:[EMAIL PROTECTED]>
For additional commands, e-mail: <mailto:[EMAIL PROTECTED]>
