【问题标题】:Combine @WebMcvTest with @PathVariable annotated controllers in Spring Boot 1.41在 Spring Boot 1.41 中将 @WebMcvTest 与 @PathVariable 注释控制器结合使用
【发布时间】:2017-03-07 12:53:18
【问题描述】:

此问题的示例应用程序在这里:https://github.com/olemerdy-fa/webmvctest

我正在使用 Spring Boot 1.4.1 引导一个新项目。我尝试利用这个出色框架的新功能,尤其是对我的应用程序“切片”进行(相当)单元测试的能力。

我现在在 @Controller 声明 @PathVariable 注释方法时使用 @WebMvcTest 功能。

确实,@WebMvcTest 应该引导单个控制器和 MockMvc 测试工具,而不提供任何其他内容。使用@MockBean,提供模拟作为依赖注入到这个控制器中仍然很容易。

但是 @PathVariable 注释参数的类型是,例如,其转换器通常由 Spring Data 注册的 JPA 实体呢?

加入此问题的示例项目包含一些示例:

  • MyEntity 是一个简单的 JPA 实体,MyEntityRepository 是它的 Spring Data 关联存储库
  • Webmvctest1Controller 有一个 load 方法从路径中检索 id 并调用自己的 MyEntityRepository.findOne(id) 方法
  • Webmvctest1ControllerUnitTest 通过模拟 MyEntityRepository 来测试这个控制器,一切顺利
  • Webmvctest2Controller 有一个 load 方法,其中 @PathVariable 注释 MyEntity 由 Spring Data 注册转换器查找

    @RestController
    public class Webmvctest2Controller {
    
        @RequestMapping("load2/{id}")
        public MyEntity load2(@PathVariable("id") MyEntity myEntity) {
            return myEntity;
        }
    
    }
    
  • Webmvctest2ControllerUnitTest 是我卡住的地方,因为我不知道如何在仍然使用MockMvc 的同时提供模拟实体作为参数

    @RunWith(SpringRunner.class)
    @WebMvcTest(Webmvctest2Controller.class)
    public class Webmvctest2ControllerUnitTest {
    
        @Autowired
        private MockMvc mvc;
    
        @Test
        public void load2() throws Exception {
    
            // How do I mock converter to PathVariable here?
    
            mvc.perform(get("/load2/123").accept(MediaType.APPLICATION_JSON))
                    .andExpect(status().isOk())
                    .andExpect(content().json("{id:123,name:'My Entity 123'}"));
        }
    
    }
    

这会失败并出现org.springframework.web.method.annotation.MethodArgumentConversionNotSupportedException 异常

    2016-10-25 14:27:55.699  WARN 20753 --- [           main] .w.s.m.s.DefaultHandlerExceptionResolver : Failed to convert request element: org.springframework.web.method.annotation.MethodArgumentConversionNotSupportedException: Failed to convert value of type [java.lang.String] to required type [com.stackoverflow.MyEntity]; nested exception is java.lang.IllegalStateException: Cannot convert value of type [java.lang.String] to required type [com.stackoverflow.MyEntity]: no matching editors or conversion strategy found

    MockHttpServletRequest:
          HTTP Method = GET
          Request URI = /load2/123
           Parameters = {}
              Headers = {Accept=[application/json]}

    Handler:
                 Type = com.stackoverflow.Webmvctest2Controller
               Method = public com.stackoverflow.MyEntity com.stackoverflow.Webmvctest2Controller.load2(com.stackoverflow.MyEntity)

    Async:
        Async started = false
         Async result = null

    Resolved Exception:
                 Type = org.springframework.web.method.annotation.MethodArgumentConversionNotSupportedException

    ModelAndView:
            View name = null
                 View = null
                Model = null

    FlashMap:
           Attributes = null

    MockHttpServletResponse:
               Status = 500
        Error message = null
              Headers = {}
         Content type = null
                 Body =
        Forwarded URL = null
       Redirected URL = null
              Cookies = []
    Tests run: 1, Failures: 1, Errors: 0, Skipped: 0, Time elapsed: 0.375 sec <<< FAILURE! - in com.stackoverflow.Webmvctest2ControllerUnitTest
    load2(com.stackoverflow.Webmvctest2ControllerUnitTest)  Time elapsed: 0.015 sec  <<< FAILURE!
    java.lang.AssertionError: Status expected:<200> but was:<500>
            at org.springframework.test.util.AssertionErrors.fail(AssertionErrors.java:54)
            at org.springframework.test.util.AssertionErrors.assertEquals(AssertionErrors.java:81)
            at org.springframework.test.web.servlet.result.StatusResultMatchers$10.match(StatusResultMatchers.java:664)
            at org.springframework.test.web.servlet.MockMvc$1.andExpect(MockMvc.java:171)
            at com.stackoverflow.Webmvctest2ControllerUnitTest.load2(Webmvctest2ControllerUnitTest.java:28)
  • WebmvctestApplicationTests 表明,当应用程序完全启动时,这两种情况下一切都很好

知道如何在保留@PathVariable 实体参数的同时仅使用@WebMvcTest 测试我的网络切片吗? 谢谢:)

【问题讨论】:

  • " @WebMvcTest 应该引导单个控制器和 MockMvc 测试工具,而不提供任何其他内容。" 你基本上回答了你自己的问题,所以我没有确定您期望的答案。你不需要@WebMvcTest 来使用MockMvc。更重要的是:请求没有正文,那么为什么该方法需要一个实体作为参数呢?
  • 这里给出的示例很简单地显示了正在发生的事情。在现实世界的代码中,该方法不会简单地转发实体,而是可以修改它、请求子属性、使用多种可转换的方法。实际上,如果在请求签名中加载实体是一个问题,那么对于转换器未在 Spring 本地注册的任何类型(例如:Joda Money),该问题仍然有效。
  • 这里,我们只能在加载所有内容时测试我们的控制器。我正在寻找一种保持切片 (docs.spring.io/spring-boot/docs/current/reference/html/…) 精神的解决方案,它只加载我们正在测试的层的少数服务(这里只加载 Web 部件,而不是数据或服务层)。我们能做些什么来只加载 Web 控制器并模拟其所有依赖项而不修改原始控制器代码(我想保留 @PathVariable 丰富的类型并让 Spring 处理转换)?
  • 对于未在 Spring 本地注册的转换器:"@WebMvcTest 将自动配置 Spring MVC 基础结构并将扫描的 bean 限制为... HandlerMethodArgumentResolver"。至于“我正在寻找一种保持切片精神的解决方案” - 再说一遍:你不需要@WebMvcTest。你可以自己设置MockMvc
  • 关于 MockMvc 可以单独引导这一事实是绝对正确的。我想依靠@WebMvcTest 来限制扫描bean 的范围。非常感谢您指出 Spring 扫描 HandlerMethodArgumentResolver,我将尝试添加一个仅为此测试注册的自定义范围,看看它是否能很好地满足我们的需要:)

标签: spring-mvc spring-boot spring-test


【解决方案1】:

感谢@zeroflagL 暗示可能对HandlerMethodArgumentResolver 做出贡献,我想出了一个似乎适合我需要的解决方案。在控制器中贡献一个处理模拟实体注入的自定义转换器似乎可以完成这项工作

    @RunWith(SpringRunner.class)
    @WebMvcTest(Webmvctest2Controller.class)
    public class Webmvctest2ControllerUnitTest {

        @Autowired
        private MockMvc mvc;

        @Test
        public void load2() throws Exception {
            mvc.perform(get("/load2/123").accept(MediaType.APPLICATION_JSON))
                    .andExpect(status().isOk())
                    .andExpect(content().json("{id:123,name:'My Entity'}"));
        }

        @TestConfiguration
        static class InternalConfig {

            @Bean
            WebMvcConfigurer configurer() {
                return new WebMvcConfigurerAdapter() {
                    @Override
                    public void addFormatters(FormatterRegistry registry) {
                        registry.addConverter(String.class, MyEntity.class, id -> {
                            if (id.equals("123")) {
                                MyEntity myEntity = new MyEntity(123);
                                myEntity.setName("My Entity");
                                return myEntity;
                            }
                            throw new IllegalArgumentException();
                        });
                    }
                };
            }
        }
    }

它并不完美,因为模拟实体不能由测试方法本身提供,但它仍然允许 web-unit-test 引导和运行我想保留。

【讨论】:

    猜你喜欢
    • 2011-12-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-03-12
    • 1970-01-01
    • 2019-01-18
    • 1970-01-01
    相关资源
    最近更新 更多