java - 如何在 @WebMvcTest 单元测试中将 PersistentEntityResourceAssembler 注入到自定义 @RepositoryRestController 的请求方法中
问题描述
概述
如何在@WebMvcTest
单元测试期间将 PersistentEntityResourceAssembler 正确注入到自定义 REST 控制器的请求方法中?
第一个问题。提前道歉,可能只是错过了一些愚蠢的事情。
GitHub 上提供的示例代码。
奥利弗·吉尔克,你在哪里?:P
细节
我有一个用@RepositoryRestController
. 它的一些请求方法有一个PersistentEntityResourceAssembler
注入作为参数,所以我可以返回 HAL 资源。
@RepositoryRestController
@RestController
@RequestMapping(produces = MediaTypes.HAL_JSON_VALUE )
public class ReportRestController {
private final ReportRepository reportRepository;
public ReportRestController(ReportRepository reportRepository) {
this.reportRepository = reportRepository;
}
@GetMapping(path = "/custom/reports/{id}")
public ResponseEntity<?> customReportsEndpoint(@PathVariable("id") Long id,
PersistentEntityResourceAssembler entityAssembler) {
return ResponseEntity.ok(entityAssembler.toResource(reportRepository.findById(id)
.orElseThrow(() -> new IllegalArgumentException("No report found with ID: " + id))));
}
}
如果我正在运行集成测试( @SpringBootTest
),这可以正常工作:
import static org.hamcrest.CoreMatchers.*;
import static org.mockito.BDDMockito.any;
import static org.mockito.BDDMockito.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class CustomRestControllerIntegrationTest {
private static final String REPORT_NAME = "Report 1";
@MockBean
private ReportRepository repository;
@Autowired
private MockMvc mvc;
@Test
public void thisTestPasses() throws Exception {
given(repository.findById(any())).willReturn(Optional.of(new Report(1L, REPORT_NAME, new User(1L, "pbriggs"))));
mvc.perform(get("/custom/reports/1"))
.andDo(print())
.andExpect(status().isOk())
.andExpect(jsonPath("$.name", equalTo(REPORT_NAME)))
.andExpect(jsonPath("$._links.enteredBy").exists())
.andReturn().getResponse();
}
}
但是,如果我正在运行单元测试( @WebMvcTest
),则PersistentEntityResourceAssembler
由于“实体”未正确注入而无法构建。
引起:org.springframework.beans.BeanInstantiationException:无法实例化[org.springframework.data.rest.webmvc.PersistentEntityResourceAssembler]:构造函数抛出异常;嵌套异常是 java.lang.IllegalArgumentException:实体被标记为 @NonNull 但为空
GitHub中可用的完整堆栈跟踪(已达到字符限制)
代码:
import static org.hamcrest.Matchers.*;
import static org.mockito.BDDMockito.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
@RunWith(SpringRunner.class)
@WebMvcTest
@EnableSpringDataWebSupport
public class CustomRestControllerUnitTest1 {
@Autowired
private MockMvc mvc;
@MockBean
private ReportRepository repository;
@Test
public void thisTestFails() throws Exception {
Report report = new Report(1L,"Report 1", new User(1L,"pbriggs"));
given(repository.findById(1L)).willReturn(Optional.of(report));
// Fails with:
//
// Caused by: org.springframework.beans.BeanInstantiationException:
// Failed to instantiate [org.springframework.data.rest.webmvc.PersistentEntityResourceAssembler]:
// Constructor threw exception; nested exception is java.lang.IllegalArgumentException:
// entities is marked @NonNull but is null
MvcResult mvcResult = mvc.perform(get("/custom/reports/1").accept(MediaTypes.HAL_JSON_VALUE))
.andDo(print())
.andExpect(status().isOk())
.andExpect(jsonPath("$.name", is("Report 1")))
.andReturn();
}
}
我认为这是因为RepositoryRestMvcAutoConfiguration
(因此反过来RepositoryRestMvcConfiguration
)永远不会被加载。这是正确的行为,@WebMvcTest
因为它只加载与 Web 相关的 bean(@Controller、@ControllerAdvice、@JsonComponent、Converter/GenericConverter、Filter、WebMvcConfigurer 和 HandlerMethodArgumentResolver)。
此外,我在GitHub 上的示例代码中运行单元测试的时间大约有一半时间发生堆栈溢出。有点奇怪,因为它非常不确定..
再说一遍,如何在单元测试PersistentEntityResourceAssembler
期间正确地注入自定义 REST 控制器的请求方法?@WebMvcTest
要求
- 这必须在单元测试中运行;我不想启动整个应用程序上下文
- 返回的 json 有效负载必须是 HAL 格式的文档(必须有
_links
节等)
研究/相关问题
1) 添加@EnableSpringDataWebSupport
StackOverflow 问题:如何在 Spring 中为 RepositoryRestController 设置单元测试?
这个问题/答案建议添加@EnableSpringDataWebSupport
,但这使我的控制器永远不会被实例化。也许是因为它使用了@Configuration
一个单元测试并且没有在一个完整的容器中运行?我不知道。
2018 年 10 月 28 日更新:上述问题还导致在 JIRA 中创建文档请求。
2) 更改PersistentEntityResourceAssembler
为RepositoryEntityLinks
StackOverflow 问题:测试使用 PersistentEntityResourceAssembler 的自定义 RepositoryRestController
该问题的答案建议将其更改为a并手动生成链接。我需要像使用PersistentEntityResourceAssembler
RepositoryEntityLinks
PersistentEntityResourceAssembler
3) 添加@BasePathAwareController
到控制器
StackOverflow 问题: 带有参数 PersistentEntityResourceAssembler 的 RestController 的 JUnit
其中一个答案指向我建议添加的DATAREST-657@BasePathAwareController
,但这并没有改变任何东西。
4) 嘲笑PersistentEntityResourceAssembler
StackOverflow 问题:Rest Controller 与 spring-data-rest RepositoryRestResource
这个答案建议嘲笑PersistentEntityResourceAssembler
和其他东西,但我无法让它工作。我会得到以下异常:
org.springframework.web.util.NestedServletException: Request processing failed; nested exception is org.springframework.http.converter.HttpMessageConversionException: Type definition error: [simple type, class org.mockito.internal.junit.DefaultStubbingLookupListener]; nested exception is com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No serializer found for class org.mockito.internal.junit.DefaultStubbingLookupListener and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS) (through reference chain: org.springframework.data.rest.webmvc.PersistentEntityResource["persistentEntity"]->org.springframework.data.mapping.PersistentEntity$MockitoMock$2115290768["mockitoInterceptor"]->org.mockito.internal.creation.bytebuddy.MockMethodInterceptor["mockHandler"]->org.mockito.internal.handler.InvocationNotifierHandler["mockSettings"]->org.mockito.internal.creation.settings.CreationSettings["stubbingLookupListeners"]->java.util.ArrayList[0])
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:982)
at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:866)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:635)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:851)
at org.springframework.test.web.servlet.TestDispatcherServlet.service(TestDispatcherServlet.java:71)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:742)
at org.springframework.mock.web.MockFilterChain$ServletFilterProxy.doFilter(MockFilterChain.java:166)
at org.springframework.mock.web.MockFilterChain.doFilter(MockFilterChain.java:133)
at org.springframework.test.web.servlet.MockMvc.perform(MockMvc.java:165)
at com.prestonb.edu.CustomRestControllerUnitTest2.thisTestFails(CustomRestControllerUnitTest2.java:78)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
at org.mockito.internal.junit.JUnitRule$1.evaluateSafely(JUnitRule.java:52)
at org.mockito.internal.junit.JUnitRule$1.evaluate(JUnitRule.java:43)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
Caused by: org.springframework.http.converter.HttpMessageConversionException: Type definition error: [simple type, class org.mockito.internal.junit.DefaultStubbingLookupListener]; nested exception is com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No serializer found for class org.mockito.internal.junit.DefaultStubbingLookupListener and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS) (through reference chain: org.springframework.data.rest.webmvc.PersistentEntityResource["persistentEntity"]->org.springframework.data.mapping.PersistentEntity$MockitoMock$2115290768["mockitoInterceptor"]->org.mockito.internal.creation.bytebuddy.MockMethodInterceptor["mockHandler"]->org.mockito.internal.handler.InvocationNotifierHandler["mockSettings"]->org.mockito.internal.creation.settings.CreationSettings["stubbingLookupListeners"]->java.util.ArrayList[0])
at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.writeInternal(AbstractJackson2HttpMessageConverter.java:291)
at org.springframework.http.converter.AbstractGenericHttpMessageConverter.write(AbstractGenericHttpMessageConverter.java:102)
at org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor.writeWithMessageConverters(AbstractMessageConverterMethodProcessor.java:272)
at org.springframework.web.servlet.mvc.method.annotation.HttpEntityMethodProcessor.handleReturnValue(HttpEntityMethodProcessor.java:224)
at org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite.handleReturnValue(HandlerMethodReturnValueHandlerComposite.java:82)
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:119)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:891)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:797)
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:991)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:925)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:974)
... 34 more
Caused by: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No serializer found for class org.mockito.internal.junit.DefaultStubbingLookupListener and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS) (through reference chain: org.springframework.data.rest.webmvc.PersistentEntityResource["persistentEntity"]->org.springframework.data.mapping.PersistentEntity$MockitoMock$2115290768["mockitoInterceptor"]->org.mockito.internal.creation.bytebuddy.MockMethodInterceptor["mockHandler"]->org.mockito.internal.handler.InvocationNotifierHandler["mockSettings"]->org.mockito.internal.creation.settings.CreationSettings["stubbingLookupListeners"]->java.util.ArrayList[0])
at com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:77)
at com.fasterxml.jackson.databind.SerializerProvider.reportBadDefinition(SerializerProvider.java:1191)
at com.fasterxml.jackson.databind.DatabindContext.reportBadDefinition(DatabindContext.java:312)
at com.fasterxml.jackson.databind.ser.impl.UnknownSerializer.failForEmpty(UnknownSerializer.java:71)
at com.fasterxml.jackson.databind.ser.impl.UnknownSerializer.serialize(UnknownSerializer.java:33)
at com.fasterxml.jackson.databind.ser.impl.IndexedListSerializer.serializeContents(IndexedListSerializer.java:119)
at com.fasterxml.jackson.databind.ser.impl.IndexedListSerializer.serialize(IndexedListSerializer.java:79)
at com.fasterxml.jackson.databind.ser.impl.IndexedListSerializer.serialize(IndexedListSerializer.java:18)
at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:727)
at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:719)
at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:155)
at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:727)
at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:719)
at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:155)
at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:727)
at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:719)
at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:155)
at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:727)
at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:719)
at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:155)
at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:727)
at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:719)
at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:155)
at com.fasterxml.jackson.databind.ser.DefaultSerializerProvider._serialize(DefaultSerializerProvider.java:480)
at com.fasterxml.jackson.databind.ser.DefaultSerializerProvider.serializeValue(DefaultSerializerProvider.java:319)
at com.fasterxml.jackson.databind.ObjectWriter$Prefetch.serialize(ObjectWriter.java:1396)
at com.fasterxml.jackson.databind.ObjectWriter.writeValue(ObjectWriter.java:913)
at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.writeInternal(AbstractJackson2HttpMessageConverter.java:285)
... 45 more
org.springframework.data.rest.webmvc.PersistentEntityResource["persistentEntity"]
->org.springframework.data.mapping.PersistentEntity$MockitoMock$1659608278["mockitoInterceptor"]
->org.mockito.internal.creation.bytebuddy.MockMethodInterceptor["mockHandler"]
->org.mockito.internal.handler.InvocationNotifierHandler["mockSettings"]
->org.mockito.internal.creation.settings.CreationSettings["stubbingLookupListeners"]
->java.util.ArrayList[0])
代码:
import static org.hamcrest.CoreMatchers.*;
import static org.mockito.BDDMockito.any;
import static org.mockito.BDDMockito.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
public class CustomRestControllerUnitTest2 {
@Mock
private PersistentEntityResourceAssembler assembler;
@Mock
private PersistentEntityResourceAssemblerArgumentResolver assemblerResolver;
@Mock
private PersistentEntity<Report, ?> entity;
@InjectMocks
private ReportRestController controller;
private MockMvc mvc;
@Rule
public MockitoRule rule = MockitoJUnit.rule();
@Mock
private ReportRepository repository;
@Before
public void setup() {
this.mvc = MockMvcBuilders.standaloneSetup(controller)
.setCustomArgumentResolvers(assemblerResolver)
.build();
}
@Test
public void thisTestFails() throws Exception {
Report report = new Report(1L,"Report 1", new User(1L,"pbriggs"));
given(repository.findById(1L)).willReturn(Optional.of(report));
given(assemblerResolver.supportsParameter(any())).willReturn(true);
given(assemblerResolver.resolveArgument(any(), any(), any(), any())).willReturn(assembler);
given(assembler.toResource(report)).willReturn(PersistentEntityResource.build(report, entity).build());
MvcResult mvcResult = mvc.perform(get("/custom/reports/1").accept(MediaTypes.HAL_JSON_VALUE))
.andDo(print())
.andExpect(status().isOk())
.andExpect(jsonPath("$.name", is("Report 1")))
.andExpect(jsonPath("$.name").exists())
.andExpect(jsonPath("$._links.enteredBy").exists())
.andReturn();
}
}
因此,我向 MockMvc 添加了一个自定义参数解析器,该解析器修复了该问题,但我的 JSON 不再被正确格式化为 HAL:
@Test
public void thisTestFails() throws Exception {
Report report = new Report(1L,"Report 1", new User(1L,"pbriggs"));
given(repository.findById(1L)).willReturn(Optional.of(report));
given(assemblerResolver.supportsParameter(any())).willReturn(true);
given(assemblerResolver.resolveArgument(any(), any(), any(), any())).willReturn(assembler);
given(assembler.toResource(report)).willReturn(PersistentEntityResource.build(report, entity).build());
MvcResult mvcResult = mvc.perform(get("/custom/reports/1").accept(MediaTypes.HAL_JSON_VALUE))
.andDo(print())
.andExpect(status().isOk())
.andExpect(jsonPath("$.name", is("Report 1")))
.andExpect(jsonPath("$.name").exists())
// Fails:
//
// Caused by: com.jayway.jsonpath.PathNotFoundException: Missing property in path $['_links']
.andExpect(jsonPath("$._links.enteredBy").exists())
.andReturn();
/*
* Expected (HAL document):
*
* {
* "name" : "Report 1",
* "_links" : {
* "self" : {
* "href" : "http://localhost/reports/1"
* },
* "report" : {
* "href" : "http://localhost/reports/1"
* },
* "enteredBy" : {
* "href" : "http://localhost/reports/1/enteredBy"
* }
* }
* }
*
* Actual (Normal json):
*
* {
* "id": 1,
* "name": "Report 1",
* "enteredBy": {
* "id": 1,
* "username": "pbriggs"
* }
* // plus a bunch of mockito properties
* }
*/
}
解决方案
推荐阅读
- c# - how to rollback all SqlCommands which already executed with SqlTransaction?
- azure - 使用复制对象创建多个 Azure VM
- javascript - 如何在 JavaScript/TypeScript 中将字符串转换为 Long(不长)
- linux - 更改 IP 后 RIAK 节点不启动
- javascript - 如何检查此 dropzone.js 引导示例中是否已添加文件?
- python - 从破折号分隔的字符串创建多级字典(不知道有多深)
- amazon-web-services - 使用 tls 私钥自动配置 EC2 实例
- reactjs - 我在与 expo 进行本机反应时出错:“语法错误意外令牌”错误。任何帮助解决它?谢谢
- javascript - Chrome 扩展用 newtab.html 覆盖 newtab,然后访问 cookie
- ios - 使用 iOS UNUserNotificationCenter,如何设置闹钟的开始和结束时间?