JAW Speak

Jonathan Andrew Wolter

Wrestling the Untestable Into Testable Code: Example with Test Driven SimpleTagSupport for custom JSP Tag

with one comment

Reading time: 3 – 5 minutes

Let’s say app servers must be stateless. There is no affinity for a user pinned to a particular app server, so two requests will likely be handled by different Tomcats, Jetty’s, etc. This means nothing can go in HttpSession (we also can’t use clustering). And when it’s ecommerce, we can’t require cookies or that might cost millions in lost revenue. Given these constraints, you have one valid option left: custom url rewriting over all our links (GET’s) and with hidden form fields (POST’s).

You can edit every link and every form to pass some unique id around. But what if you have two or three id’s that need to pass around for legacy reasons? Sounds like a custom tag would be nice. Something you can put in your jsp’s (oh yeah, let’s say we’re using jsp’s too).

This introduces me to the testability challenge. Write your own tag extending SimpleTagSupport that would ensure parameters were always in a link, and write another tag to write out the three hidden input fields inside every form.

But, SimpleTagSupport is part of the good old servlet and jsp API, so it’s very bloated with context objects, deep inheritance, callbacks, and cruft that makes for difficult unit testing. Just look at the public interface, one method: public void doTag() throws JspException, IOException.

Here’s my test driven test case (with 100% coverage, through the public API), which takes advantage of Spring MVC’s MockHttpServletRequest and MockHttpServletResponse objects (which are really fakes). I’m also using Mockito, which I prefer over EasyMock and JMock.

package com.jawspeak.dotcom.tag;
 
import static org.junit.Assert.*;
import org.junit.Before;
import org.junit.Test;
import static org.mockito.Mockito.*;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.mock.web.MockPageContext;
 
import javax.servlet.jsp.tagext.JspFragment;
import java.io.StringWriter;
import java.io.Writer;
 
public class StatefulLinkTagTest_SettingSessionId {
	private StatefulLinkTag statefulLinkTag = new StatefulLinkTag();
	private MockHttpServletRequest request = new MockHttpServletRequest();
	private MockHttpServletResponse response = new MockHttpServletResponse();
	private static final String SESSION_ID = "ADFHE13";
 
	@Before
	public void given() throws Exception {
		request.setAttribute("sid", SESSION_ID);
		statefulLinkTag.setJspContext(new MockPageContext(null, request, response));
		JspFragment jspBodyFragment = mock(JspFragment.class);
		doAnswer(new FakeJspBodyAnswerer()).when(jspBodyFragment)
                        .invoke((Writer) anyObject());
		statefulLinkTag.setJspBody(jspBodyFragment);
	}
 
	@Test
	public void shouldRenderLinkWhenThereIsNoParameter() throws Exception {
		statefulLinkTag.setHref("/myPage.html");
		statefulLinkTag.doTag();
		String contentAsString = response.getContentAsString();
		assertEquals("<a href=\"/myPage.html?sid=" + 
                     SESSION_ID + "\">link text body</a>", contentAsString);
	}
 
	@Test
	public void shouldRenderLinkWhenThereIsAlreadyAParameter() throws Exception {
		statefulLinkTag.setHref("/myPage.html?my_param=foo");
		statefulLinkTag.doTag();
		String contentAsString = response.getContentAsString();
		assertEquals("<a href=\"/myPage.html?my_param=foo&sid=" + 
                    SESSION_ID + "\">link text body</a>", contentAsString);
	}
 
	/** Mockito Answer implementation to manipulate the parameters we pass 
	 * into JspFragment.invoke() due to the ugly servlet API. */
	private static class FakeJspBodyAnswerer implements Answer {
		public Object answer(InvocationOnMock invocationOnMock) throws Throwable {
			StringWriter writer = (StringWriter) invocationOnMock.getArguments()[0];
			writer.write("link text body");
			return null;
		}
	}
}

As for the implementation, it’s pretty straightforward after writing the tests. It doesn’t show covering multiple ID’s appended to the link, but it is now easy to test drive implementing them.

package com.jawspeak.dotcom.tag;
 
import javax.servlet.http.HttpServletRequest;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.PageContext;
import javax.servlet.jsp.tagext.SimpleTagSupport;
import java.io.IOException;
import java.io.StringWriter;
 
public class StatefulLinkTag extends SimpleTagSupport {
	private String href;
 
	public void setHref(String href) {
		this.href = href;
	}
 
	@Override
	public void doTag() throws JspException, IOException {
		PageContext pageContext = (PageContext) getJspContext();
		getJspContext().getOut().print(createTagText(
                      (HttpServletRequest) pageContext.getRequest()));
	}
 
	private String createTagText(HttpServletRequest request)
                       throws IOException, JspException {
		StringWriter stringWriter = new StringWriter();
		stringWriter.append("<a href=\"");
		stringWriter.append(href);
		if (href.indexOf("?") > 0) {
			stringWriter.append("&sid=");
		} else {
			stringWriter.append("?sid=");
		}
		stringWriter.append((String)request.getAttribute("sid"));
		stringWriter.append("\">");
		getJspBody().invoke(stringWriter);
		stringWriter.append("</a>");
		return stringWriter.toString();
	}
}

Questions, comments? Want to bash Java and tell me why I should be using Ruby, Erlang or Scala? For more about the SimpleTagSupport interface check here.

Bookmark and Share

Written by Jonathan

March 24th, 2009 at 9:06 pm

One Response to 'Wrestling the Untestable Into Testable Code: Example with Test Driven SimpleTagSupport for custom JSP Tag'

Subscribe to comments with RSS or TrackBack to 'Wrestling the Untestable Into Testable Code: Example with Test Driven SimpleTagSupport for custom JSP Tag'.

  1. This is really great stuff. No bashing required!

    Nick

    17 Apr 15 at 3:44 pm

Leave a Reply