views:

1806

answers:

3

With Spring MVC, you can specify that a particular URL will handled by a particular method, and you can specify that particular parameters will map to particular arguments, like so:

@Controller
public class ImageController {

   @RequestMapping("/getImage")
   public String getImage( @RequestParam("imageId") int imageId, Map<String,Object> model ) {
      model.put("image",ImageService.getImage(imageId));
   }

}

This is all well and good, but now I want to test that an http request with an imageId parameter will invoke this method correctly. In other words, I want a test that will break if I remove or change any of the annotations. Is there a way to do this?

It is easy to test that getImage works correctly. I could just create an ImageController and invoke getImage with appropriate arguments. However, this is only one half of the test. The other half of the test must be whether getImage() will be invoked by the Spring framework when an appropriate HTTP request comes in. I feel like I also need a test for this part, especially as my @RequestMapping annotations become more complex and invoke complex parameter conditions.

Could you show me a test that will break if I remove line 4, @RequestMapping("getImage")?

+1  A: 

I think testing your code will break if you remove any annotation is totally out of your project scope, this belongs to Spring devs and I'm pretty sure we have in their source code some unit tests that assure the requests are redirected correctly. You can rely on the framework stability.

If you want to is to test that a given controller has an specific @RequestMapping and one of its parameter has an specific @RequestParam and throw an alert if somebody in the future changes this (for a Test Harness) you can program an Assert class like this:

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;

import java.lang.reflect.Method;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

public class AssertControllers {
    // Verifies the class has Controller annotation
    public static void verifyController(Class<?> clazz) throws Exception {
     Controller contAnnotation = clazz.getAnnotation(Controller.class);
     assertNotNull(contAnnotation);
    }

    // Verifies a given method has an specific RequestMapping
    public static void verifyRequestMapping(Method method, String mapping)
      throws Exception {
     RequestMapping rmAnnotation = method
       .getAnnotation(RequestMapping.class);
     assertEquals(rmAnnotation.value()[0], mapping);
    }
}

You can add a new method for verifying parameters annotations

victor hugo
I think Oliver is really trying to verify that he set up the @RequestMapping annotations properly: "If I send a request to /foo/bar, will the right method get called?" -- it's especially helpful for TDD. I came here looking for the same thing because I'm getting 404s and I'm still not sure which part of my application is misconfigured.
Ed Brannin
That was exactly the intention, Ed ;).
Oliver Gierke
+5  A: 

You could use AnnotationMethodHandlerAdapter and its handle method programmatically. This will resolve the method for the given request and execute it. Unfortunately this is a little indirect. Actually there is a private class called ServletHandlerMethodResolver in AMHA that is responsible for just resolving the method for a given request. I just filed a request for improvement on that topic, as I really would like to see this possible, too.

In the meantime you could use e.g. EasyMock to create a mock of your controller class, expect the given method to be invoked and hand that mock to handle.

Controller:

@Controller
public class MyController {

  @RequestMapping("/users")
  public void foo(HttpServletResponse response) {

    // your controller code
  }
}

Test:

public class RequestMappingTest {

  private MockHttpServletRequest request;
  private MockHttpServletResponse response;
  private MyController controller;
  private AnnotationMethodHandlerAdapter adapter;


  @Before
  public void setUp() {

    controller = EasyMock.createNiceMock(MyController.class);

    adapter = new AnnotationMethodHandlerAdapter();
    request = new MockHttpServletRequest();
    response = new MockHttpServletResponse();
  }


  @Test
  public void testname() throws Exception {

    request.setRequestURI("/users");

    controller.foo(response);
    EasyMock.expectLastCall().once();
    EasyMock.replay(controller);

    adapter.handle(request, response, controller);

    EasyMock.verify(controller);
  }
}

Regards, Ollie

Oliver Gierke
+2  A: 

Ollie's solution covers testing the specific example of an annotation but what about the wider question of how to test all the other various Spring MVC annotations. My approach (that can be easily extended to other annotations) would be

import static org.springframework.test.web.ModelAndViewAssert.*;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({/* include live config here
    e.g. "file:web/WEB-INF/application-context.xml",
    "file:web/WEB-INF/dispatcher-servlet.xml" */})
public class MyControllerIntegrationTest {

    @Inject
    private ApplicationContext applicationContext;

    private MockHttpServletRequest request;
    private MockHttpServletResponse response;
    private HandlerAdapter handlerAdapter;
    private MyController controller;

    @Before
    public void setUp() {
       request = new MockHttpServletRequest();
       response = new MockHttpServletResponse();
       handlerAdapter = applicationContext.getBean(HandlerAdapter.class);
       // I could get the controller from the context here
       controller = new MyController();
    }

    @Test
    public void testFoo() throws Exception {
       request.setRequestURI("/users");
       final ModelAndView mav = handlerAdapter.handle(request, response, 
           controller);
       assertViewName(mav, null);
       assertAndReturnModelAttributeOfType(mav, "image", Image.class);
    }
}

I've also written a blog entry about integration testing Spring MVC annotations.

scarba05