Writing unit test cases is a really necessary step with the agile practices, but writing test cases for Servlets is little bit hard; because those are intend to run inside a container. There are few test frameworks such as
Apache Cactus ,
HttpUnit,
XMLUnit to facilitate to these requirements, specially
ServletUnit provided by
HttpUnit is a very smart and easy way to test Servlets.
But
ServletUnit does not facilitate to inject Spring ApplicationContext into Servlet Context and hence testing Spring injected Servlets is not possible with
ServletUnit (AFAIK).
But
Spring-mock package provide some of the mock web component such as MockHttpServletRequest , MockHttpServletResponse and MockServletContext ..etc to test web applications. Also
Spring test package provide features like auto-wiring, transactional supports etc. It is possible to use those two packages to test Spring injected Servlets.
Spring Test class such as AbstractDependencyInjectionSpringContextTests
Can be used to retrieve ApplicationContext for our test cases then we can set it in to a MockServletContext object as a attribute with key value WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE so that the Spring context is available to our servlet.
Normally in a container WebApplicationContextUtil is used to retrieve
WebAplicationContext through the ServletContext using above WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE attribute.
One of the basic problem with this approach is WebApplicationContextUtils only accepts ApplicationContext classes that implement the WebApplicationContext interface, here AbstractDependencyInjectionSpringContextTests returns a org.springframework.context.support.GenericApplicationContext object as the ApplicationContext . Since GenericApplicationContext doesn’t implement the WebApplicationContext interface we can’t use this ApplicatioContext.
There are few remedies here.
1. Instead of Spring test package use kind of a WebApplicationContext class to load the test configuration. I tried with XmlWebApplicationContext but after several attempts I gave up.
String[] ctx= new String[]{"/WEB-INF/app.xml"};
webApplicationContext = new XmlWebApplicationContext();
webApplicationContext.setConfigLocations( ctx);
webApplicationContext.setServletContext(
new MockServletContext(
new FileSystemResourceLoader() ) );
webApplicationContext.refresh();2. Use Spring test package to load the ApplicationContext (e.g – AbstractDependencyInjectionSpringContextTests ) and manually copy the bean definitions in to a WebApplicationContext object and set it in to the ServletContext.
Above second approach is worked with me and I used GenericWebApplicationContext as my WebApplicationContext. If you are interested in go through
the following code segments.
This is a simple servlet I used to test, it utilize Spring bean called “helloService”.
public class SimpleService extends HttpServlet {
private HelloService helloService;
public void init(ServletConfig servletConfig) throws ServletException {
super.init(servletConfig);
// retrieve Spring AppContext through the ServletContext.
ServletContext servletContext = servletConfig.getServletContext();
ApplicationContext applicationContext = WebApplicationContextUtils
.getWebApplicationContext(servletContext);
// retrieve Spring service
helloService = (HelloService) applicationContext.getBean("helloService");
}
public void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
resp.setContentType("text/xml");
PrintWriter pr = resp.getWriter();
String text = req.getParameter("text");
System.out.println(" recived : " + text);
pr.println(helloService.doService(text));
pr.flush();
pr.close();
}
}
Now we can create a reusable abstract Test class for our approach.
public abstract class AbstractSpringServeletTest extends
AbstractDependencyInjectionSpringContextTests {
private ServletConfig servletConfig;
protected abstract void init() throws Exception;
protected void onSetUp() throws Exception {
ServletContext sctx = new MockServletContext();
servletConfig = new MockServletConfig(sctx, "simple");
sctx.setAttribute(
WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE,
getWebApplicationContext());
init();
}
protected WebApplicationContext getWebApplicationContext() {
ApplicationContext ctx = getApplicationContext();
System.out.println(ctx.getClass().getName());
GenericWebApplicationContext wac = (GenericWebApplicationContext) BeanUtils
.instantiateClass(GenericWebApplicationContext.class);
String[] defNames = ctx.getBeanDefinitionNames();
for (String defName : defNames) {
wac.getBeanFactory().registerSingleton(defName,
ctx.getBean(defName));
}
return wac;
}
protected ServletConfig getServletConfig() {
return servletConfig;
}
protected ServletContext getServletContext() {
return servletConfig.getServletContext();
}
}
Here I derived AbstractSpringServeletTest class from AbstractDependencyInjectionSpringContextTests but it is possible to derive from any other test classes according to the requirements. Finally we can write concrete TestCase for out SimpleServlet as follows.
public class ServletTest extends AbstractSpringServeletTest {
private SimpleService simpleServlet;
protected String[] getConfigLocations() {
return new String[] { "classpath:app.xml" };
}
public void init() throws Exception {
simpleServlet = new SimpleService();
simpleServlet.init(getServletConfig());
}
public void testDoGet() throws Exception, IOException {
MockHttpServletRequest request = new MockHttpServletRequest(
getServletContext(), "GET", "/service");
request.addParameter("text", "sagara");
MockHttpServletResponse response = new MockHttpServletResponse();
simpleServlet.doGet(request, response);
String res = response.getContentAsString();
System.out.println(res);
assertEquals("Hello : sagara", res.trim());
}
}