Thursday, March 22, 2007

Mock Objects and Unit Testing - A Brief Tutorial

What are mock objects and why are they important?

At times, it may be difficult to unit test a piece of code you're interested in because it may have an outside dependency - a dependency that may not really have anything to do with the code that you're trying to unit test.

For example, suppose you have a servlet with a doGet() method you've overridden that you would like to unit test. Typically, servlets run inside a servlet engine such as Apache Tomcat. Because unit tests have to run in an automated and repeatable way you have to try to avoid having to start up Tomcat every time you have to run your unit test.
This is where mock objects come in.

What if you could fool your servlet's doGet() method into "thinking" that is being called by a servlet container when in actuality it is being called by something else - your unit test? In order to do this, we need to pass in mock
implementations of HttpServletRequest and HttpServletResponse into the servlet's doGet() method. Our mock implementations of HttpServletRequest and HttpServletResponse in essence will act as Trojan horses and fool the doGet() method into thinking that it is dealing with the real, servlet engine-created implementations.

At this point, it may not be 100% clear how to do this. But here's a hint: the thing that makes mock objects work are interfaces. In fact, HttpServletRequest and HttpServletResponse are not classes, they're interfaces. So all we have to do is to create mock implementations of these interfaces and we will be good to go.

There's nothing wrong with creating mock implementations from scratch. In most cases you will have to do just that because there will be no other alternatives. However, in our case creating workable mock implementations of
HttpServletRequest and HttpServletResponse is a lot of work. Luckily, the Spring Framework comes with mock implementations that we can use. The mock objects we need are packaged in spring-mock.jar - they're called MockHttpSerlvetRequest and MockHttpServletResponse, respectively.

On with the example...Here we're going to write a simple Java servlet and a corresponding unit tests that utilize mock objects.

Source Code


///////////////////////////// SERVLET START //////////////////////////////

package com.treyvus.mockobjects.example.servlet;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 @author Yevgeniy Treyvus
 */
public class HelloWorldServlet extends HttpServlet {

  public static final String NAME_NOT_PROVIDED_ERROR_MSG = "Name not provided.";
  public static final String NAME_PARAM = "name";
  /**
   
   */
  private static final long serialVersionUID = 232520223006073222L;

  protected void doGet(HttpServletRequest req, HttpServletResponse resthrows ServletException, IOException {
    String yourName = req.getParameter(NAME_PARAM);
    if(yourName==null || yourName.trim().length()==0) {
      res.sendError(HttpServletResponse.SC_BAD_REQUEST, NAME_NOT_PROVIDED_ERROR_MSG);
      return;
    }
    res.getWriter().println(generateGreeting(yourName));
  }

  public static String generateGreeting(String yourName) {
    return "Hello " + yourName + "!";
  }
}

///////////////////////////// SERVLET END ////////////////////////////////
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
///////////////////////////// UNIT TEST START ////////////////////////////

package com.treyvus.mockobjects.example.servlet;

import javax.servlet.http.HttpServletResponse;

import junit.framework.TestCase;

import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;

/**
 @author Yevgeniy Treyvus
 */
public class HelloWorldServletTest extends TestCase {

  public void testHelloWorldServletSendsBadRequestResponseIfNoNameParameterProvided() throws Exception {
    HelloWorldServlet helloWorldServlet = new HelloWorldServlet();
    MockHttpServletRequest req = new MockHttpServletRequest();
    MockHttpServletResponse res = new MockHttpServletResponse();
    helloWorldServlet.doGet(req, res);
    assertEquals(HttpServletResponse.SC_BAD_REQUEST, res.getStatus());
    assertEquals(HelloWorldServlet.NAME_NOT_PROVIDED_ERROR_MSG, res.getErrorMessage());
  }

  public void testHelloWorldServletSendsSuccessResponseIfNameParameterIsProvided() throws Exception {
    final String name = "Yevgeniy";
    HelloWorldServlet helloWorldServlet = new HelloWorldServlet();
    MockHttpServletRequest req = new MockHttpServletRequest();
    req.setParameter(HelloWorldServlet.NAME_PARAM, name);
    MockHttpServletResponse res = new MockHttpServletResponse();
    helloWorldServlet.doGet(req, res);
    assertEquals(HttpServletResponse.SC_OK, res.getStatus());
    assertEquals(HelloWorldServlet.generateGreeting(name), res.getContentAsString().trim());
  }  
}

///////////////////////////// UNIT TEST END ////////////////////////////


The servlet's business logic is simple. If the servlet parameter 'name' is provided then the servlet generates a greeting. If the parameter is not in the request, then the servlet sets the response status to error. Now, do we really need to run Tomcat to test this behavior? Of course not.

The key is that the servlet doesn't know that we're using mock implementations of HttpServletRequest and HttpServletResponse in our unit test.



my site

No comments: