on software development and possibly other stuff

Testing Spring MVC Annotated Controllers from JUnit

4 comments
Let me begin by clarifying that this post is more about testing the correctness of annotations applied to Spring Controllers rather than unit testing the behavior of the controller methods.

Annotations on Spring Controllers are commonly tested manually, together with the views. Aside from the usual concerns that come with it, manual testing also involves significant overhead with

having to redeploy binaries and restarting the web server in-between fixes. While "hot-deploy" reduces a lot of these concerns, it still leaves some room for improvement.
 
AnnotationMethodHandlerAdapter

Starting with version 2.5, Spring MVC comes with a class called AnnotationMethodHandlerAdapter which can be used to programatically invoke an annotated Spring Controller. The handle() method from this class accepts an HttpServletRequest, an HttpServletResponse, and an Object representing the Controller to invoke. It returns an instance of ModelAndView.

We can use AnnotationMethodHandlerAdapter within a JUnit test case to simulate a web container handling a request to the Spring Controller.
@Test
public void testController() throws Exception {
  ModelAndView modelAndView = handlerAdapter.handle(request, response, myController);
  assertEquals("index", modelAndView.getViewName());
}
We can mock HttpServletRequest and HttpServletResponse using popular mocking frameworks such Mockito or use the built-in MockHttpServletRequest and MockHttpServletResponse from the Spring Test module. This example uses the built-in mocked objects from Spring Test.

Using AnnotationMethodHandlerAdapter together with Generics, we can create an abstract class which serves as a parent class to all Spring Controller unit test cases:
@Ignore("abstract test case")
public abstract class AbstractControllerTest<T> {
  private final T controller;
  private final AnnotationMethodHandlerAdapter handlerAdapter;
  private MockHttpServletRequest request;
  private MockHttpServletResponse response;

  public AbstractControllerTest(T controller) {
    handlerAdapter = new AnnotationMethodHandlerAdapter();
    this.controller = controller;
  }

  @Before
  public void setUp() {
    response = new MockHttpServletResponse();
    request = new MockHttpServletRequest();
  }

  protected T getController() {
    return controller;
  }

  protected AnnotationMethodHandlerAdapter getHandlerAdapter() {
    return handlerAdapter;
  }

  protected MockHttpServletRequest getRequest() {
    return request;
  }

  protected MockHttpServletResponse getResponse() {
    return response;
  }

  protected ModelAndView invokeController() throws Exception {
    return handlerAdapter.handle(request, response, controller);
  }
}
As a parent class, it takes care of the boiler plate code involved in setting-up and invoking the controller.

Example

Let's say we have a controller which returns a view named "index" and populates the current user's username as a model attribute:
@Controller
public class MyController {
  @RequestMapping(value = "/", method = RequestMethod.GET)
  public String getHomePage(Principal principal, Model model) {
    if (principal != null) {
      model.addAttribute("username", principal.getName());
    }
    return "index";
  }
}
To test this, we create a concrete subclass of AbstractControllerTest and assign MyController as the generic type:
@RunWith(BlockJUnit4ClassRunner.class)
public class MyControllerTest 
  extends AbstractControllerTest<MyController> {

  public MyControllerTest() {
    super(new MyController());
  }

  @Test
  public void testGetHomePage() throws Exception {
    getRequest().setMethod("GET");
    getRequest().setServletPath("/");
    getRequest().setUserPrincipal(new HttpPrincipal("username", "realm"));

    ModelAndView modelAndView = invokeController();
    assertEquals("index", modelAndView.getViewName());
    assertNotNull(modelAndView.getModelMap().get("username"));
    assertEquals("username", modelAndView.getModelMap().get("username"));
  }
}
Line 6 creates an instance of MyController and passes it as a parameter to the parent class' constructor.

Lines 11 to 13 sets-up the HTTP request by assigning the request method, request URL, and a fake Principal.

Line 15 invokes the request against the Spring Controller.

Lines 16-18 asserts the correctness of the returned ModelAndView.

The above snippets are available from Google Code.

On my next post, I will explain how to test Spring Controllers returning a JSON response by extending the above classes.

4 comments :

  1. I know this is a very helpful site but the thing is I am a newbie to MVC spring . Although this site helps to clear my understanding a bit. It is not giving me any idea about how to make this piece of code work.. The code is missing packages that re required.. can you please also add the packages required... it would be better if it can show the fully compilable program instead of just a part from the working version..

    ReplyDelete
  2. Thanks for the feedback. To be honest, I purposely try to avoid tutorial-like posts because that's not the real purpose of this blog.

    But I do understand your concern that's why if appropriate, I normally include a link to a working source code at the end of each post.

    ReplyDelete
  3. Just used this, and it works great... thanks for the sample...

    ReplyDelete
  4. Really nice tutorial. Thanks so much :)

    ReplyDelete