如何使用@PathVariable对Spring MVC控制器进行unit testing?

我有一个类似于这个简单的注释控制器:

@Controller public class MyController { @RequestMapping("/{id}.html") public String doSomething(@PathVariable String id, Model model) { // do something return "view"; } } 

我想用这样的unit testing来testing它:

 public class MyControllerTest { @Test public void test() { MockHttpServletRequest request = new MockHttpServletRequest(); request.setRequestURI("/test.html"); new AnnotationMethodHandlerAdapter() .handle(request, new MockHttpServletResponse(), new MyController()); // assert something } } 

问题是AnnotationMethodHandlerAdapter.handler()方法抛出一个exception:

 java.lang.IllegalStateException: Could not find @PathVariable [id] in @RequestMapping at org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter$ServletHandlerMethodInvoker.resolvePathVariable(AnnotationMethodHandlerAdapter.java:642) at org.springframework.web.bind.annotation.support.HandlerMethodInvoker.resolvePathVariable(HandlerMethodInvoker.java:514) at org.springframework.web.bind.annotation.support.HandlerMethodInvoker.resolveHandlerArguments(HandlerMethodInvoker.java:262) at org.springframework.web.bind.annotation.support.HandlerMethodInvoker.invokeHandlerMethod(HandlerMethodInvoker.java:146) 

从3.2版本开始,有一个合适的方法来testing这个问题,方法简单明了。 你将能够做到这样的事情:

 @RunWith(SpringJUnit4ClassRunner.class) @WebAppConfiguration @ContextConfiguration("servlet-context.xml") public class SampleTests { @Autowired private WebApplicationContext wac; private MockMvc mockMvc; @Before public void setup() { this.mockMvc = webAppContextSetup(this.wac).build(); } @Test public void getFoo() throws Exception { this.mockMvc.perform(get("/foo").accept("application/json")) .andExpect(status().isOk()) .andExpect(content().mimeType("application/json")) .andExpect(jsonPath("$.name").value("Lee")); } } 

有关更多信息,请参阅http://blog.springsource.org/2012/11/12/spring-framework-3-2-rc1-spring-mvc-test-framework/

我会根据Spring参考手册中的术语调用集成testing后的结果。 如何做这样的事情:

 import static org.springframework.test.web.ModelAndViewAssert.*; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration({/* include live config here eg "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 testDoSomething() throws Exception { request.setRequestURI("/test.html"); final ModelAndView mav = handlerAdapter.handle(request, response, controller); assertViewName(mav, "view"); // assert something } } 

有关更多信息,我写了一篇关于集成testingSpring MVC注释的博客文章 。

一个有前途的testingSpring MVC的框架https://github.com/SpringSource/spring-test-mvc

exception消息引用了一个“feed”variables,这个variables在你的示例代码中不存在,它可能是由你没有向我们显示的东西引起的。

另外,你的testing是testingSpring 你自己的代码。 这真的是你想要做的吗?

假设Spring能够工作(它是这样做的),并且只是testing你自己的类,也就是直接调用MyController.doSomething() 。 这是注释方法的一个好处 – 您不需要使用模拟请求和响应,只需使用域POJO。

假设你正在使用Spring 3.0.x.

在这里,我build议Emil和scarba05答案使用春季testing,而不是spring-test-mvc合并。 如果您使用的是Spring 3.2.x或更高版本,请跳过这个答案并参考spring-test-mvc示例

MyControllerWithParameter.java

 @Controller public class MyControllerWithParameter { @RequestMapping("/testUrl/{pathVar}/some.html") public String passOnePathVar(@PathVariable String pathVar, ModelMap model){ model.addAttribute("SomeModelAttribute",pathVar); return "viewName"; } } 

MyControllerTest.java

 import static org.springframework.test.web.ModelAndViewAssert.assertViewName; import java.util.HashMap; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.web.ModelAndViewAssert; import org.springframework.web.servlet.HandlerAdapter; import org.springframework.web.servlet.HandlerMapping; import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = {"file:src\\main\\webapp\\WEB-INF\\spring\\services\\servlet-context.xml" }) public class MyControllerTest { private MockHttpServletRequest request; private MockHttpServletResponse response; private HandlerAdapter handlerAdapter; @Before public void setUp() throws Exception { request = new MockHttpServletRequest(); response = new MockHttpServletResponse(); this.handlerAdapter = applicationContext.getBean(AnnotationMethodHandlerAdapter.class); } // Container beans private MyControllerWithParameter myController; private ApplicationContext applicationContext; public ApplicationContext getApplicationContext() { return applicationContext; } @Autowired public void setApplicationContext(ApplicationContext applicationContext) { this.applicationContext = applicationContext; } public MyControllerWithParameter getMyController() { return myController; } @Autowired public void setMyController(MyControllerWithParameter myController) { this.myController = myController; } @Test public void test() throws Exception { request.setRequestURI("/testUrl/Irrelavant_Value/some.html"); HashMap<String, String> pathvars = new HashMap<String, String>(); // Populate the pathVariable-value pair in a local map pathvars.put("pathVar", "Path_Var_Value"); // Assign the local map to the request attribute concerned with the handler mapping request.setAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, pathvars); final ModelAndView modelAndView = this.handlerAdapter.handle(request, response, myController); ModelAndViewAssert.assertAndReturnModelAttributeOfType(modelAndView, "SomeModelAttribute", String.class); ModelAndViewAssert.assertModelAttributeValue(modelAndView, "SomeModelAttribute", "Path_Var_Value"); ModelAndViewAssert.assertViewName(modelAndView, "viewName"); } 

}

我发现你可以手动在请求对象中插入一个PathVariable映射。 这显然是不理想的,但似乎工作。 在你的例子中,像这样的:

 @Test public void test() { MockHttpServletRequest request = new MockHttpServletRequest(); request.setRequestURI("/test.html"); HashMap<String, String> pathvars = new HashMap<String, String>(); pathvars.put("id", "test"); request.setAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, pathvars); new AnnotationMethodHandlerAdapter().handle(request, new MockHttpServletResponse(), new MyController()); // assert something } 

我一定会有兴趣find一个更好的select。

我不确定我的原始答案是要帮助@PathVariable。 我刚刚尝试过testing@PathVariable,并得到以下exception:

org.springframework.web.bind.annotation.support.HandlerMethodInvocationException:无法调用处理程序方法[public org.springframework.web.servlet.ModelAndView test.MyClass.myMethod(test.SomeType)]; 嵌套的exception是java.lang.IllegalStateException:在@RequestMapping中找不到@PathVariable [parameterName]

原因是请求中的pathvariables被拦截器parsing。 以下方法适用于我:

 import static org.springframework.test.web.ModelAndViewAssert.*; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration({"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; @Before public void setUp() throws Exception { this.request = new MockHttpServletRequest(); this.response = new MockHttpServletResponse(); this.handlerAdapter = applicationContext.getBean(HandlerAdapter.class); } ModelAndView handle(HttpServletRequest request, HttpServletResponse response) throws Exception { final HandlerMapping handlerMapping = applicationContext.getBean(HandlerMapping.class); final HandlerExecutionChain handler = handlerMapping.getHandler(request); assertNotNull("No handler found for request, check you request mapping", handler); final Object controller = handler.getHandler(); // if you want to override any injected attributes do it here final HandlerInterceptor[] interceptors = handlerMapping.getHandler(request).getInterceptors(); for (HandlerInterceptor interceptor : interceptors) { final boolean carryOn = interceptor.preHandle(request, response, controller); if (!carryOn) { return null; } } final ModelAndView mav = handlerAdapter.handle(request, response, controller); return mav; } @Test public void testDoSomething() throws Exception { request.setRequestURI("/test.html"); request.setMethod("GET"); final ModelAndView mav = handle(request, response); assertViewName(mav, "view"); // assert something else } 

我已经在集成testingspring mvc注释中添加了一篇新的博客文章