如何将@WebMvcTest 用于使用自动装配的 ConversionService 的控制器?
How can I use @WebMvcTest for a Controller that uses an autowired ConversionService?
在 Spring 引导应用程序中,我有两个 POJO,Foo
和 Bar
,以及一个 BarToFooConverter
,它看起来像:
@Component
public class BarToFooConverter implements Converter<Bar, Foo> {
@Override
public Foo convert(Bar bar) {
return new Foo(bar.getBar());
}
}
我还有一个使用转换器的控制器:
@RestController("test")
public class TestController {
@Autowired
private ConversionService conversionService;
@RequestMapping(method = RequestMethod.PUT)
@ResponseBody
public Foo put(@RequestBody Bar bar) {
return conversionService.convert(bar, Foo.class);
}
}
我想用 @WebMvcTest
测试这个控制器,比如:
@WebMvcTest
@RunWith(SpringRunner.class)
public class TestControllerTest {
@Autowired
private MockMvc mockMvc;
@Test
public void test() throws Exception {
mockMvc.perform(
put("/test")
.contentType(MediaType.APPLICATION_JSON)
.content("{\"bar\":\"test\"}"))
.andExpect(status().isOk());
}
}
但是当我 运行 这个的时候,我发现我的 BarToFooConverter
没有注册到 ConversionService
:
Caused by: org.springframework.core.convert.ConverterNotFoundException: No converter found capable of converting from type [com.example.demo.web.Bar] to type [com.example.demo.web.Foo]
at org.springframework.core.convert.support.GenericConversionService.handleConverterNotFound(GenericConversionService.java:324)
at org.springframework.core.convert.support.GenericConversionService.convert(GenericConversionService.java:206)
at org.springframework.core.convert.support.GenericConversionService.convert(GenericConversionService.java:187)
at com.example.demo.web.TestController.put(TestController.java:15)
这似乎是有道理的,因为,根据 the Javadoc:
Using this annotation will disable full auto-configuration and instead apply only configuration relevant to MVC tests (i.e. @Controller, @ControllerAdvice, @JsonComponent Filter, WebMvcConfigurer and HandlerMethodArgumentResolver beans but not @Component, @Service or @Repository beans).
然而,reference guide 略有不同,说 @WebMvcTest
包括 Converter
s:
@WebMvcTest auto-configures the Spring MVC infrastructure and limits scanned beans to @Controller, @ControllerAdvice, @JsonComponent, Converter, GenericConverter, Filter, WebMvcConfigurer, and HandlerMethodArgumentResolver. Regular @Component beans are not scanned when using this annotation.
这里的参考指南似乎不正确 - 还是我的 Converter
注册不正确?
我还尝试在测试中模拟 ConversionService
:
@WebMvcTest
@RunWith(SpringRunner.class)
public class TestControllerTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private ConversionService conversionService;
@Test
public void test() throws Exception {
when(conversionService.convert(any(Bar.class), eq(Foo.class))).thenReturn(new Foo("test"));
mockMvc.perform(
put("/test")
.contentType(MediaType.APPLICATION_JSON)
.content("{\"bar\":\"test\"}"))
.andExpect(status().isOk());
}
}
但现在 Spring 抱怨我的模拟 ConversionService
覆盖了默认模拟:
Caused by: java.lang.IllegalStateException: @Bean method WebMvcConfigurationSupport.mvcConversionService called as a bean reference for type [org.springframework.format.support.FormattingConversionService] but overridden by non-compatible bean instance of type [org.springframework.core.convert.ConversionService$$EnhancerByMockitoWithCGLIB$$da4e303a]. Overriding bean of same name declared in: null
at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.obtainBeanInstanceFromFactory(ConfigurationClassEnhancer.java:402)
at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.intercept(ConfigurationClassEnhancer.java:361)
...
理想情况下,我想使用我原来的方法,在我的测试中使用真正的转换器而不是模拟 ConversionService
,但使用 @WebMvcTest
来限制启动组件的范围,所以我也尝试在 @WebMvcTest
注释中使用 includeFilter
:
@WebMvcTest(includeFilters = @ComponentScan.Filter(type = FilterType.REGEX, pattern = "com.example.demo.web.Bar*"))
但它仍然失败并显示原始 'No converter found capable of converting...' 错误消息。
这感觉像是一个很常见的要求 - 我错过了什么?
您可以在 @Before
注释方法中手动注册您的转换器。您所要做的就是注入 GenericConversionService
and call addConverter(new BarToFooConverter())
以使转换器可解析。在这种情况下,您可以摆脱模拟部分。您的测试可能如下所示:
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.core.convert.support.GenericConversionService;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@WebMvcTest
@RunWith(SpringRunner.class)
public class TestControllerTest {
@Autowired
private MockMvc mockMvc;
@Autowired
private GenericConversionService conversionService;
@Before
public void setup() {
conversionService.addConverter(new BarToFooConverter());
}
@Test
public void test() throws Exception {
mockMvc.perform(
put("/test")
.contentType(MediaType.APPLICATION_JSON)
.content("{\"bar\":\"test\"}"))
.andExpect(status().isOk());
}
}
替代解决方案:Spring 自版本 1.4.0 提供 collection of test-related auto-configurations and one of these auto-configurations is @AutoConfigureMockMvc
配置 MockMvc
组件,该组件可与注入转换器一起正常工作组件。
在 Spring 引导应用程序中,我有两个 POJO,Foo
和 Bar
,以及一个 BarToFooConverter
,它看起来像:
@Component
public class BarToFooConverter implements Converter<Bar, Foo> {
@Override
public Foo convert(Bar bar) {
return new Foo(bar.getBar());
}
}
我还有一个使用转换器的控制器:
@RestController("test")
public class TestController {
@Autowired
private ConversionService conversionService;
@RequestMapping(method = RequestMethod.PUT)
@ResponseBody
public Foo put(@RequestBody Bar bar) {
return conversionService.convert(bar, Foo.class);
}
}
我想用 @WebMvcTest
测试这个控制器,比如:
@WebMvcTest
@RunWith(SpringRunner.class)
public class TestControllerTest {
@Autowired
private MockMvc mockMvc;
@Test
public void test() throws Exception {
mockMvc.perform(
put("/test")
.contentType(MediaType.APPLICATION_JSON)
.content("{\"bar\":\"test\"}"))
.andExpect(status().isOk());
}
}
但是当我 运行 这个的时候,我发现我的 BarToFooConverter
没有注册到 ConversionService
:
Caused by: org.springframework.core.convert.ConverterNotFoundException: No converter found capable of converting from type [com.example.demo.web.Bar] to type [com.example.demo.web.Foo]
at org.springframework.core.convert.support.GenericConversionService.handleConverterNotFound(GenericConversionService.java:324)
at org.springframework.core.convert.support.GenericConversionService.convert(GenericConversionService.java:206)
at org.springframework.core.convert.support.GenericConversionService.convert(GenericConversionService.java:187)
at com.example.demo.web.TestController.put(TestController.java:15)
这似乎是有道理的,因为,根据 the Javadoc:
Using this annotation will disable full auto-configuration and instead apply only configuration relevant to MVC tests (i.e. @Controller, @ControllerAdvice, @JsonComponent Filter, WebMvcConfigurer and HandlerMethodArgumentResolver beans but not @Component, @Service or @Repository beans).
然而,reference guide 略有不同,说 @WebMvcTest
包括 Converter
s:
@WebMvcTest auto-configures the Spring MVC infrastructure and limits scanned beans to @Controller, @ControllerAdvice, @JsonComponent, Converter, GenericConverter, Filter, WebMvcConfigurer, and HandlerMethodArgumentResolver. Regular @Component beans are not scanned when using this annotation.
这里的参考指南似乎不正确 - 还是我的 Converter
注册不正确?
我还尝试在测试中模拟 ConversionService
:
@WebMvcTest
@RunWith(SpringRunner.class)
public class TestControllerTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private ConversionService conversionService;
@Test
public void test() throws Exception {
when(conversionService.convert(any(Bar.class), eq(Foo.class))).thenReturn(new Foo("test"));
mockMvc.perform(
put("/test")
.contentType(MediaType.APPLICATION_JSON)
.content("{\"bar\":\"test\"}"))
.andExpect(status().isOk());
}
}
但现在 Spring 抱怨我的模拟 ConversionService
覆盖了默认模拟:
Caused by: java.lang.IllegalStateException: @Bean method WebMvcConfigurationSupport.mvcConversionService called as a bean reference for type [org.springframework.format.support.FormattingConversionService] but overridden by non-compatible bean instance of type [org.springframework.core.convert.ConversionService$$EnhancerByMockitoWithCGLIB$$da4e303a]. Overriding bean of same name declared in: null
at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.obtainBeanInstanceFromFactory(ConfigurationClassEnhancer.java:402)
at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.intercept(ConfigurationClassEnhancer.java:361)
...
理想情况下,我想使用我原来的方法,在我的测试中使用真正的转换器而不是模拟 ConversionService
,但使用 @WebMvcTest
来限制启动组件的范围,所以我也尝试在 @WebMvcTest
注释中使用 includeFilter
:
@WebMvcTest(includeFilters = @ComponentScan.Filter(type = FilterType.REGEX, pattern = "com.example.demo.web.Bar*"))
但它仍然失败并显示原始 'No converter found capable of converting...' 错误消息。
这感觉像是一个很常见的要求 - 我错过了什么?
您可以在 @Before
注释方法中手动注册您的转换器。您所要做的就是注入 GenericConversionService
and call addConverter(new BarToFooConverter())
以使转换器可解析。在这种情况下,您可以摆脱模拟部分。您的测试可能如下所示:
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.core.convert.support.GenericConversionService;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@WebMvcTest
@RunWith(SpringRunner.class)
public class TestControllerTest {
@Autowired
private MockMvc mockMvc;
@Autowired
private GenericConversionService conversionService;
@Before
public void setup() {
conversionService.addConverter(new BarToFooConverter());
}
@Test
public void test() throws Exception {
mockMvc.perform(
put("/test")
.contentType(MediaType.APPLICATION_JSON)
.content("{\"bar\":\"test\"}"))
.andExpect(status().isOk());
}
}
替代解决方案:Spring 自版本 1.4.0 提供 collection of test-related auto-configurations and one of these auto-configurations is @AutoConfigureMockMvc
配置 MockMvc
组件,该组件可与注入转换器一起正常工作组件。