JaVa
   

Working with Nested Tags

The JSP specification allows tags to be nested within other tags. Most of the time, nested tags operate independently. However, tag designers can build child tags that interact with their enclosing parent tags. (The relationship does not work the other way; enclosing tags cannot act on their children unless they communicate through shared page data.) To adequately test this complex situation, we need two sets of tests. First, simulate the effects of the child on the parent and verify that the parent behaves as expected. Second, test the child tag to be sure that the parent tag provides access to the correct services or data. This section will walk through both sets of tests. Two tags form the basis for our example: parent and child. The parent element (taking a single attribute, "name") simply prints out a message stating its name and whether it contains children. The child elements each obtain the name of their parent and print a message declaring who they belong to. The tag might be used like this:

<example:parent name="Papa Smurf">
 <example:child/>
 <example:child/>
</example:parent>


The generated output would be

Child tag nested within parent named 'Papa Smurf'. Child tag nested within parent named 'Papa Smurf'. Parent tag named 'Papa Smurf' does contain children.


The tag handler classes (minus class imports) is shown in the following listing.

public class ParentTag extends TagSupport{
 private boolean containsChildren;
 private String name;
 public boolean getContainsChildren() {
 return containsChildren;
 }
 public void setContainsChildren(boolean containsChildren) {
 this.containsChildren = containsChildren;
 }
 public String getName() {
 return name;
 }
 public void setName(String name) {
 this.name = name;
 }
 public int doAfterBody() throws JspTagException{
 JspWriter out = pageContext.getOut();
 try{
 out.print("Parent tag named '" + name + "' does ");
 if(!containsChildren){
 out.print("not ");
 }
 out.print("contain children.");
 }
 catch(java.io.IOException e){
 throw new JspTagException(e.toString()); }
 return this.SKIP_BODY;
 }
}
public class ChildTag extends TagSupport{
 public int doStartTag() throws JspTagException{
 JspWriter out = pageContext.getOut();
 ParentTag parent = (ParentTag)TagSupport.findAncestorWithClass(this, ParentTag.class);
 parent.setContainsChildren(true);
 String parentName= parent.getName();
 try{
 out.print("Child tag nested within parent named '" + parentName+"'.");
 }
 catch(java.io.IOException e){
 throw new JspTagException(e.toString()); }
 return this.EVAL_BODY_INCLUDE;
 } }


The child tag interacts with the parent by setting a property in the parent tag (containsChildren). Depending on the value of containsChildren, the message printed from ParentTag's doAfterBody() method will differ. Instead of attempting to test a full nested interaction right off the bat, we begin by testing this interaction. The following listing shows the setUp() method (the test uses the BodyContent trick discussed in the section "Server-Side Assertion Facilities") and two test methods (each exercising a different state of the ParentTag class).

public void setUp(){
 tag = new ParentTag();
 tag.setPageContext(pageContext);
 tag.setName("testName");
 tempOut = pageContext.pushBody();
}
public void testNoKids()throws Exception {
 tag.setContainsChildren(false);
 tag.doAfterBody();
 String result = tempOut.getString();
 /*should print "does not contain children"*/
 assertTrue(result.indexOf("not") > -1); }
public void testKids()throws Exception{
 tag.setContainsChildren(true);
 tag.doAfterBody();
 String result = tempOut.getString();
 /*should print "does contain children"*/
 assertTrue(result.indexOf("not") == -1);
}


Once we verify the basic mechanism of interaction, we can move on to the ChildTag class. The setUp() method performs some extra steps:

private ChildTag tag;
private ParentTag p;
private BodyContent tempOut;
public void setUp()throws Exception{
 startParentTag();
 tag = new ChildTag(); tag.setPageContext(this.pageContext);
 tag.setParent(p);
 /*the BodyContent trick*/
 tempOut = pageContext.pushBody();
}
public void startParentTag() throws Exception{
 p = new ParentTag();
 p.setPageContext(this.pageContext);
 p.setName("test parent name");
 p.doStartTag();
}


As you can see, setUp() creates both a ParentTag and a ChildTag class. setUp() instantiates and initializes ParentTag first, including a call to doStartTag() (which remains the default implementation in this case). Once setUp() has reached the point in parent tag's life cycle where its body would be processed, it creates the child tag and sets p (the instantiated ParentTag object) as the child tag's parent. ChildTag now awaits testing. The test method calls doStartTag() (the only implemented life-cycle method) on the child and then checks the containsChildren property of the parent tag—tag.doStartTag() should have changed the property to "true":

public void testDoStartTag() throws Exception{
 tag.doStartTag();
 /*child's doStartTag method should have modified the parent's
 containsChildren property.
 */
 assertTrue(p.getContainsChildren());
 String outputOfDoStartTag = tempOut.getString();
 assertEquals("Child tag nested within parent named '"+ p.getName()+"'.",
 outputOfDoStartTag);
}


Assuming that this first assertion passes, testDoStartTag() uses the BodyContent trick (see the section "Improving the Assertion Facilities") to obtain the output from the child tag. The test method uses a call to assertEquals() to verify that the output contains the name of the parent tag and is formatted correctly.

Having simulated the effects of the child tag upon the parent and also verified that the child tag was finding and using the parent tag correctly, our test cases cover most of the territory available to them. However, nested tag interactions can get very complex. Creating several actual-use examples in a real JSP and then examining the translated servlet yields valuable insight into how to model tag interactions in a test context.


JaVa
   
Comments