Java 如何使用@PathVariable 对 Spring MVC 控制器进行单元测试?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1401128/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-12 11:40:33  来源:igfitidea点击:

How to unit test a Spring MVC controller using @PathVariable?

javaspringunit-testingspring-mvcspring-test

提问by martiner

I have a simple annotated controller similar to this one:

我有一个与此类似的简单带注释的控制器:

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

and I want to test it with an unit test like this:

我想用这样的单元测试来测试它:

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
  }
}

The problem is that AnnotationMethodHandlerAdapter.handler() method throws an exception:

问题是 AnnotationMethodHandlerAdapter.handler() 方法抛出异常:

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)

采纳答案by Clint Eastwood

As of Spring 3.2, there is a proper way to test this, in an elegant and easy way. You will be able to do things like this:

从 Spring 3.2 开始,有一种适当的方法可以优雅且简单地测试这一点。你将能够做这样的事情:

@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"));
  }
}

For further information, take a look at http://blog.springsource.org/2012/11/12/spring-framework-3-2-rc1-spring-mvc-test-framework/

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

回答by skaffman

The exception message refers to a "feed" variable, which isn't present in your sample code, it's likely being caused by something you haven't shown us.

异常消息指的是“feed”变量,它不存在于您的示例代码中,它可能是由您未向我们展示的某些内容引起的。

Also, your test is testing Spring andyour own code. Is this really what you want to do?

此外,您的测试正在测试 Spring您自己的代码。这真的是你想做的吗?

It's better to assume that Spring works (which it does), and just test your own class, i.e. call MyController.doSomething()directly. That's one benefit of the annotation approach - you don't need to use mock requests and responses, you just use domain POJOs.

最好假设 Spring 有效(它确实有效),并且只测试您自己的类,即MyController.doSomething()直接调用。这是注释方法的一个好处——您不需要使用模拟请求和响应,您只需使用域 POJO。

回答by scarba05

I'd call what you're after an integration test based on the terminology in the Spring reference manual. How about doing something like:

我会根据 Spring 参考手册中的术语在集成测试之后称呼您。如何做这样的事情:

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 testDoSomething() throws Exception {
       request.setRequestURI("/test.html");
       final ModelAndView mav = handlerAdapter.handle(request, response, 
           controller);
       assertViewName(mav, "view");
       // assert something
    }
}

For more information I've written a blog entry about integration testing Spring MVC annotations.

有关更多信息,我写了一篇关于集成测试 Spring MVC annotations博客条目

回答by Emil Sit

I've found that you can manually insert a PathVariable mapping into the request object. This is distinctly non-ideal but appears to work. In your example, something like:

我发现您可以手动将 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
}

I'd definitely be interested in finding a better option.

我肯定有兴趣找到更好的选择。

回答by scarba05

I'm not sure my original answer is going to help with @PathVariable. I've just tried testing an @PathVariable and I get the following exception:

我不确定我的原始答案是否会对@PathVariable 有所帮助。我刚刚尝试测试@PathVariable,但出现以下异常:

org.springframework.web.bind.annotation.support.HandlerMethodInvocationException: Failed to invoke handler method [public org.springframework.web.servlet.ModelAndView test.MyClass.myMethod(test.SomeType)]; nested exception is java.lang.IllegalStateException: Could not find @PathVariable [parameterName] in @RequestMapping

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

The reason is that the path variables in the request get parsed by an interceptor. The following approach works for me:

原因是请求中的路径变量被拦截器解析。以下方法对我有用:

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
    }

I've add a new blog post on integration testing spring mvc annotations

我添加了一篇关于集成测试 spring mvc 注释的新博客文章

回答by tbruyelle

A promising framework for testing Spring MVC https://github.com/SpringSource/spring-test-mvc

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

回答by Sym-Sym

Provided you are using Spring 3.0.x.

如果您使用的是 Spring 3.0.x。

Here I suggest a merger of Emil and scarba05 answers using spring-test not spring-test-mvc. Please skip this answer and refer to spring-test-mvc examples if you are using Spring 3.2.x or later

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

MyControllerWithParameter.java

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

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");
}

}

}