Affects: 5.1.8
Spring MVC supports controllers return reactive type:
Application code
@RestController
public class DemoController {
@GetMapping
public Mono<ResponseEntity<Foobar>> get() {
return Mono.just(new ResponseEntity<>(new Foobar().setFoo("A").setBar("1"), HttpStatus.ACCEPTED));
}
}
However, testing this type of application with MockMvc doesn't work.
Test code
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class DemoApplicationTests {
@Autowired
private MockMvc client;
@Test
public void getRoot() throws Exception {
client.perform(get("/"))
.andExpect(status().isAccepted())
.andExpect(jsonPath("foo").value("A"))
.andExpect(jsonPath("bar").value("1"))
;
}
}
Test output
MockHttpServletResponse:
Status = 200
Error message = null
Headers = []
Content type = null
Body =
Forwarded URL = null
Redirected URL = null
Cookies = []
When debugging test:
- Controller is successfully called
- Reactive stack and result is properly retrieved
- ResponseEntity is never processed
MockHttpServletResponsefields are never set
When debugging application:
- It seems request and response are processed by Coyote processor
- At some future time, DispatcherServlet is called again and a
HandlerMethodReturnValueHandlerprocessResponseEntity
Comment From: loganmzz
After second debugging session, I think there's missing some actions there: https://github.com/spring-projects/spring-framework/blob/v5.1.8.RELEASE/spring-test/src/main/java/org/springframework/test/web/servlet/MockMvc.java#L184
In fact, when Catalina AsyncContext dispatch is called it creates a org.apache.catalina.core.AsyncContextImpl.AsyncRunnable which is called later on by Coyote. In fact, Catalina call ApplicationDispatcher.dispatch(servletRequest, servletResponse)
Then RequestMappingHandlerAdapter detect WebAsyncManager has a concurrent result which resolves result (i.e. ResponseEntity) which is then processed as in synchronous call.
Hope it helps to understand behaviour. I don't know how to fix it properly right now. If I have some ideas, I will try to submit a PR.
Comment From: loganmzz
Ok, I finally get usage of MockMvc in this case of reactive types.
@Test
public void getRoot() throws Exception {
MvcResult result = client.perform(get("/"))
.andExpect(reqest().asyncStarted())
.andReturn();
client.perform(asyncDispatch(result));
.andExpect(status().isAccepted())
.andExpect(jsonPath("foo").value("A"))
.andExpect(jsonPath("bar").value("1"))
;
}
Comment From: rstoyanchev
This was also recently documented in #19666.
Comment From: Hassan-Elseoudy
Ok, I finally get usage of MockMvc in this case of reactive types.
@Test public void getRoot() throws Exception { MvcResult result = client.perform(get("/")) .andExpect(reqest().asyncStarted()) .andReturn(); client.perform(asyncDispatch(result)); .andExpect(status().isAccepted()) .andExpect(jsonPath("foo").value("A")) .andExpect(jsonPath("bar").value("1")) ; }
Thank you, this saved me!