Saturday, June 2, 2012

Unit Testing Servlet with Mocks

While integration testing a servlet in a real servlet container such as tjws or jetty works well, in development, unit testing just the servlet handler methods is more efficient and provides the granularity in "unit testing". Unfortunately the HttpServletRequest and HttpServletResponse aren't so easy to work with in testing. Fortunately there are many mock frameworks that help. This example shows how mockito helps to test the behavior of the servlet under development.

HiNemoServlet.java
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class HiNemoServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("plain/text");
        resp.getOutputStream().println("Hi Nem!");
    }
}

There are two behaviors to test in this servlet:
  • setting the content type to "plain/text"
  • outputting the response "Hi Nemo!"

HiNemoServletTest.java
import org.junit.Test;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

public class HiNemoServletTest {

    @Test
    public void testDoGet() throws Exception {
        HttpServletRequest mockRequest = mock(HttpServletRequest.class);
        HttpServletResponse mockResponse = mock(HttpServletResponse.class);
        ServletOutputStream mockOutput = mock(ServletOutputStream.class);

        when(mockResponse.getOutputStream()).thenReturn(mockOutput);

        new HiNemoServlet().doGet(mockRequest, mockResponse);

        verify(mockResponse).setContentType("plain/text");
        verify(mockOutput).println("Hi Nemo!");
    }
}

Three mock objects are used in this test. The mock request simply satisfies the method call argument requirement. Since the servlet does not use the request, the mock request is untouched. The mock response and the mock outputstream verify the desired behavior in the servlet.

First, the getOutputStream() call gets stubbed with returning the mock outputstream.
when(mockResponse.getOutputStream()).thenReturn(mockOutput);

Then the servlet's doGet() method is executed with the mocks.
new HiNemoServlet().doGet(mockRequest, mockResponse);

Finally, the two desired behaviors are verified.
verify(mockResponse).setContentType("plain/text");
verify(mockOutput).println("Hi Nemo!");

In this example, I have a typo in my servlet code. Instead of "Nemo", it's "Nem". So the unit test fails with this output:
Argument(s) are different! Wanted:
servletOutputStream.println("Hi Nemo");
-> at HiNemoServletTest.testDoGet(HiNemoServletTest.java:26)
Actual invocation has different arguments:
servletOutputStream.println("Hi Nem!");
-> at HiNemoServlet.doGet(HiNemoServlet.java:13)

Expected :servletOutputStream.println("Hi Nemo!");
Actual   :servletOutputStream.println("Hi Nem!");

    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:39)
    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:27)
    at java.lang.reflect.Constructor.newInstance(Constructor.java:513)
    at HiNemoServletTest.testDoGet(HiNemoServletTest.java:26)
    ...
    ...

Fix my typo in the servlet, run the test again, and it passes this time. Job done. Let's call it a day!
(This post is written for michja.)

No comments: