20

I am following a Spring 2.5 tutorial and trying, at the same time, updating the code/setup to Spring 3.0.

In Spring 2.5 I had the HelloController (for reference):

public class HelloController implements Controller {
    protected final Log logger = LogFactory.getLog(getClass());
    public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        logger.info("Returning hello view");
        return new ModelAndView("hello.jsp");
    }
}

And a JUnit test for the HelloController (for reference):

public class HelloControllerTests extends TestCase {
    public void testHandleRequestView() throws Exception{
        HelloController controller = new HelloController();
        ModelAndView modelAndView = controller.handleRequest(null, null);
        assertEquals("hello", modelAndView.getViewName());
    }
}

But now I updated the controller to Spring 3.0, and it now uses annotations (I also added a message):

@Controller
public class HelloController {
    protected final Log logger = LogFactory.getLog(getClass());
    @RequestMapping("/hello")
    public ModelAndView handleRequest() {
        logger.info("Returning hello view");
        return new ModelAndView("hello", "message", "THIS IS A MESSAGE");
    }
}

Knowing that I am using JUnit 4.9, can some one explain me how to unit test this last controller?

nunaxe
  • 1,432
  • 2
  • 15
  • 16
  • possible duplicate of [How to unit test a Spring MVC controller using @PathVariable?](http://stackoverflow.com/questions/1401128/how-to-unit-test-a-spring-mvc-controller-using-pathvariable) – Raedwald Jan 23 '14 at 08:30

3 Answers3

25

One advantage of annotation-based Spring MVC is that they can be tested in a straightforward manner, like so:

import org.junit.Test;
import org.junit.Assert;
import org.springframework.web.servlet.ModelAndView;

public class HelloControllerTest {
   @Test
   public void testHelloController() {
       HelloController c= new HelloController();
       ModelAndView mav= c.handleRequest();
       Assert.assertEquals("hello", mav.getViewName());
       ...
   }
}

Is there any problem with this approach?

For more advanced integration testing, there is a reference in Spring documentation to the org.springframework.mock.web.

nunaxe
  • 1,432
  • 2
  • 15
  • 16
Sasha O
  • 3,710
  • 2
  • 35
  • 45
  • +1 Thank you so much Sasha. It works great. I couldn't imagine it was so simple. – nunaxe Apr 25 '11 at 10:30
  • This won't work if there are @Autowired components in HelloController – Aram Kocharyan Oct 27 '12 at 05:24
  • @AramKocharyan: in unit tests I would recommend against using Autowired and supply dependencies explicitly, via constructor or setters. If you really want all the Spring goodness you may want to look into http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/beans.html#beans-java – Sasha O Oct 29 '12 at 23:46
  • @SashaO yeah learnt that this week :P I'm just setting the autowired instance with a setter as you say, worked well. – Aram Kocharyan Oct 30 '12 at 00:48
  • I find this approach too white box. The fact that the view is called "hello" doesn't mean is doing what needs to do. We are unit testing our controllers using HttpClient. Just as if the test were the browser. – Rafael Jan 10 '13 at 07:53
  • @Rafael -- I can see why you would want to test View with the HttpClient (http://htmlunit.sourceforge.net/ is great for that BTW) but why would you want to test Controller? With Controller, you want to test that it puts the right stuff into the model and invokes the correct view. – Sasha O Jan 10 '13 at 16:57
21

With mvc:annotation-driven you have to have 2 steps: first you resolve the request to handler using HandlerMapping, then you can execute the method using that handler via HandlerAdapter. Something like:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("yourContext.xml")
public class ControllerTest {

    @Autowired
    private RequestMappingHandlerAdapter handlerAdapter;

    @Autowired
    private RequestMappingHandlerMapping handlerMapping;

    @Test
    public void testController() throws Exception {
        MockHttpServletRequest request = new MockHttpServletRequest();
        // request init here

        MockHttpServletResponse response = new MockHttpServletResponse();
        Object handler = handlerMapping.getHandler(request).getHandler();
        ModelAndView modelAndView = handlerAdapter.handle(request, response, handler);

        // modelAndView and/or response asserts here
    }
}

This works with Spring 3.1, but I guess some variant of this must exist for every version. Looking at the Spring 3.0 code, I'd say DefaultAnnotationHandlerMapping and AnnotationMethodHandlerAdapter should do the trick.

Dejan P
  • 451
  • 6
  • 4
  • 1
    @I got null pointer exception in `Object handler = handlerMapping.getHandler(request).getHandler();` How would i solve – jackyesind Jul 31 '13 at 12:16
  • @jackyesind, as suggested in the answer the request must be initialized. Something like `new MockHttpServletResponse("GET", "/your/uri");` – mks-d Aug 12 '17 at 16:58
1

You can also look into other web testing frameworks that are independent of Spring like HtmlUnit, or Selenium. You won't find any more robust strategy with JUnit alone other than what Sasha has described, except you should definitely assert the model.

hisdrewness
  • 7,599
  • 2
  • 21
  • 28