title: Spring Mvc async Rest Controller test fails if response comes from ExceptionHandler: asyncDispatch CountDownLatch was not set by TestDispatcherServlet
body: <p>I have a Spring Boot 2 MVC application that returns async results. Notice that although I'm using Mono it is not a webflux app but webmvc, so the result will be treated as a Spring MVC async result (Callable/DeferredResult). Besides, my controller defines a validator for the Post input:</p>
  
  <pre><code>@ExceptionHandler
  @ResponseStatus(HttpStatus.BAD_REQUEST)
  public Flux&lt;ObjectError&gt; exception(MethodArgumentNotValidException ex) {
      logger.error("{}", ex.getLocalizedMessage(), ex);
      return Flux.fromIterable(ex.getBindingResult().getAllErrors());
  }
  
  @InitBinder
  public void initBinder(WebDataBinder binder) {
      binder.setValidator(customerValidator);
  }
  
  @PostMapping(produces = APPLICATION_JSON_UTF8_VALUE)
  @ResponseStatus(HttpStatus.CREATED)
  public Mono&lt;Customer&gt; createCustomer(@Valid @RequestBody Customer customer) {
      return customerService.create(customer);
  }
  </code></pre>
  
  <p>When I run a test with a valid customer it passes as expected:</p>
  
  <pre><code>@WebMvcTest(CustomerRestController.class)
  @TestInstance(TestInstance.Lifecycle.PER_CLASS)
  @Import({CustomerValidator.class})
  class CustomerRestControllerTests {
  
      @Test
      void shouldCreateNewCustomer() throws Exception {
  
          Customer customer = new Customer("66666D");
          given(customerService.create(customer)).willReturn(Mono.just(customer));
  
          MvcResult asyncResult = mockMvc
                  .perform(post("/api/v1/customers")
                          .content(objectMapper.writeValueAsString(customer))
                          .accept(MediaType.APPLICATION_JSON_UTF8)
                          .contentType(MediaType.APPLICATION_JSON_UTF8))
                  .andReturn();
  
          final MvcResult result = mockMvc.perform(asyncDispatch(asyncResult))
                  .andExpect(status().isCreated())
                  .andExpect(header().string("Content-Type", MediaType.APPLICATION_JSON_UTF8_VALUE))
                  .andReturn();
  
          final Customer createdCustomer = objectMapper.readValue(result.getResponse().getContentAsString(), Customer.class);
          Assertions.assertEquals(customer, createdCustomer);
      }
  </code></pre>
  
  <p>The problem is when there is a validation error. In the following test I don't set any value for the customer id. Thus, I can see the validator and then the ExceptionHandler are executed but then the test fails:</p>
  
  <pre><code>    MvcResult asyncResult = mockMvc
              .perform(post("/api/v1/customers")
                      .content("{}")
                      .accept(MediaType.APPLICATION_JSON_UTF8)
                      .contentType(MediaType.APPLICATION_JSON_UTF8))
              .andReturn();
  
      mockMvc.perform(asyncDispatch(asyncResult))
              .andExpect(status().isBadRequest());
  </code></pre>
  
  <p>Below is the full stack trace. With a 400 status, which is correct, but the asyncDispatcherror:</p>
  
  <pre><code>2018-12-26 15:06:03.714 ERROR 8836 --- [           main] c.p.p.a.c.CustomerRestController        : Validation failed for argument [0] in public reactor.core.publisher.Mono&lt;com.codependent.customerspoc.dto.Customer&gt; com.codependent.customerspoc.api.controller.CustomerRestController.createCustomer(com.codependent.customerspoc.dto.Customer) with 3 errors: [Field error in object 'Customer' on field 'nif': rejected value [null]; codes [error.validation.customer.nif.empty.Customer.nif,error.validation.customer.nif.empty.nif,error.validation.customer.nif.empty.java.lang.String,error.validation.customer.nif.empty]; arguments []; default message [null]] 
  
  org.springframework.web.bind.MethodArgumentNotValidException: Validation failed for argument [0] in public reactor.core.publisher.Mono&lt;com.codependent.customerspoc.dto.Customer&gt; com.codependent.customerspoc.api.controller.CustomerRestController.createCustomer(com.codependent.customerspoc.dto.Customer) with 1 error: [Field error in object 'Customer' on field 'nif': rejected value [null]; codes [error.validation.customer.nif.empty.Customer.nif,error.validation.customer.nif.empty.nif,error.validation.customer.nif.empty.java.lang.String,error.validation.customer.nif.empty]; arguments []; default message [null]] 
  
      at org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.resolveArgument(RequestResponseBodyMethodProcessor.java:138) ~[spring-webmvc-5.1.3.RELEASE.jar:5.1.3.RELEASE]
      at org.springframework.web.method.support.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:126) ~[spring-web-5.1.3.RELEASE.jar:5.1.3.RELEASE]
      at org.springframework.web.method.support.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:166) ~[spring-web-5.1.3.RELEASE.jar:5.1.3.RELEASE]
      at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:134) ~[spring-web-5.1.3.RELEASE.jar:5.1.3.RELEASE]
      at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:102) ~[spring-webmvc-5.1.3.RELEASE.jar:5.1.3.RELEASE]
      at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:895) ~[spring-webmvc-5.1.3.RELEASE.jar:5.1.3.RELEASE]
      at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:800) ~[spring-webmvc-5.1.3.RELEASE.jar:5.1.3.RELEASE]
      at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) ~[spring-webmvc-5.1.3.RELEASE.jar:5.1.3.RELEASE]
      at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1038) ~[spring-webmvc-5.1.3.RELEASE.jar:5.1.3.RELEASE]
      at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:942) ~[spring-webmvc-5.1.3.RELEASE.jar:5.1.3.RELEASE]
      at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1005) [spring-webmvc-5.1.3.RELEASE.jar:5.1.3.RELEASE]
      at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:908) [spring-webmvc-5.1.3.RELEASE.jar:5.1.3.RELEASE]
      at javax.servlet.http.HttpServlet.service(HttpServlet.java:660) ~[tomcat-embed-core-9.0.13.jar:9.0.13]
      at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:882) [spring-webmvc-5.1.3.RELEASE.jar:5.1.3.RELEASE]
      at org.springframework.test.web.servlet.TestDispatcherServlet.service(TestDispatcherServlet.java:71) [spring-test-5.1.3.RELEASE.jar:5.1.3.RELEASE]
      at javax.servlet.http.HttpServlet.service(HttpServlet.java:741) ~[tomcat-embed-core-9.0.13.jar:9.0.13]
      at org.springframework.mock.web.MockFilterChain$ServletFilterProxy.doFilter(MockFilterChain.java:166) [spring-test-5.1.3.RELEASE.jar:5.1.3.RELEASE]
      at org.springframework.mock.web.MockFilterChain.doFilter(MockFilterChain.java:133) [spring-test-5.1.3.RELEASE.jar:5.1.3.RELEASE]
      at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:99) [spring-web-5.1.3.RELEASE.jar:5.1.3.RELEASE]
      at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) [spring-web-5.1.3.RELEASE.jar:5.1.3.RELEASE]
      at org.springframework.mock.web.MockFilterChain.doFilter(MockFilterChain.java:133) [spring-test-5.1.3.RELEASE.jar:5.1.3.RELEASE]
      at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:92) [spring-web-5.1.3.RELEASE.jar:5.1.3.RELEASE]
      at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) [spring-web-5.1.3.RELEASE.jar:5.1.3.RELEASE]
      at org.springframework.mock.web.MockFilterChain.doFilter(MockFilterChain.java:133) [spring-test-5.1.3.RELEASE.jar:5.1.3.RELEASE]
      at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:93) [spring-web-5.1.3.RELEASE.jar:5.1.3.RELEASE]
      at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) [spring-web-5.1.3.RELEASE.jar:5.1.3.RELEASE]
      at org.springframework.mock.web.MockFilterChain.doFilter(MockFilterChain.java:133) [spring-test-5.1.3.RELEASE.jar:5.1.3.RELEASE]
      at org.springframework.test.web.servlet.MockMvc.perform(MockMvc.java:182) [spring-test-5.1.3.RELEASE.jar:5.1.3.RELEASE]
      at com.codependent.customerspoc.api.controller.CustomerRestControllerTests.shouldFailCreateNewCustomerValidation(CustomerRestControllerTests.java:174) [test-classes/:na]
      at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_161]
      at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_161]
      at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_161]
      at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_161]
      at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:532) [junit-platform-commons-1.3.2.jar:1.3.2]
      at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:115) [junit-jupiter-engine-5.3.2.jar:5.3.2]
      at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$6(TestMethodTestDescriptor.java:171) [junit-jupiter-engine-5.3.2.jar:5.3.2]
      at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:72) ~[junit-platform-engine-1.3.2.jar:1.3.2]
      at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:167) [junit-jupiter-engine-5.3.2.jar:5.3.2]
      at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:114) [junit-jupiter-engine-5.3.2.jar:5.3.2]
      at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:59) [junit-jupiter-engine-5.3.2.jar:5.3.2]
      at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$4(NodeTestTask.java:108) ~[junit-platform-engine-1.3.2.jar:1.3.2]
      at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:72) ~[junit-platform-engine-1.3.2.jar:1.3.2]
      at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:98) ~[junit-platform-engine-1.3.2.jar:1.3.2]
      at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:74) ~[junit-platform-engine-1.3.2.jar:1.3.2]
      at java.util.ArrayList.forEach(ArrayList.java:1257) ~[na:1.8.0_161]
      at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38) ~[junit-platform-engine-1.3.2.jar:1.3.2]
      at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$4(NodeTestTask.java:112) ~[junit-platform-engine-1.3.2.jar:1.3.2]
      at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:72) ~[junit-platform-engine-1.3.2.jar:1.3.2]
      at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:98) ~[junit-platform-engine-1.3.2.jar:1.3.2]
      at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:74) ~[junit-platform-engine-1.3.2.jar:1.3.2]
      at java.util.ArrayList.forEach(ArrayList.java:1257) ~[na:1.8.0_161]
      at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38) ~[junit-platform-engine-1.3.2.jar:1.3.2]
      at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$4(NodeTestTask.java:112) ~[junit-platform-engine-1.3.2.jar:1.3.2]
      at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:72) ~[junit-platform-engine-1.3.2.jar:1.3.2]
      at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:98) ~[junit-platform-engine-1.3.2.jar:1.3.2]
      at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:74) ~[junit-platform-engine-1.3.2.jar:1.3.2]
      at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:32) ~[junit-platform-engine-1.3.2.jar:1.3.2]
      at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57) ~[junit-platform-engine-1.3.2.jar:1.3.2]
      at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:51) ~[junit-platform-engine-1.3.2.jar:1.3.2]
      at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:220) ~[junit-platform-launcher-1.3.2.jar:1.3.2]
      at org.junit.platform.launcher.core.DefaultLauncher.lambda$execute$6(DefaultLauncher.java:188) ~[junit-platform-launcher-1.3.2.jar:1.3.2]
      at org.junit.platform.launcher.core.DefaultLauncher.withInterceptedStreams(DefaultLauncher.java:202) ~[junit-platform-launcher-1.3.2.jar:1.3.2]
      at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:181) ~[junit-platform-launcher-1.3.2.jar:1.3.2]
      at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:128) ~[junit-platform-launcher-1.3.2.jar:1.3.2]
      at com.intellij.junit5.JUnit5IdeaTestRunner.startRunnerWithArgs(JUnit5IdeaTestRunner.java:74) ~[junit5-rt.jar:na]
      at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47) ~[junit-rt.jar:na]
      at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242) ~[junit-rt.jar:na]
      at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70) ~[junit-rt.jar:na]
  
  
  MockHttpServletRequest:
        HTTP Method = POST
        Request URI = /api/v1/customers
         Parameters = {}
            Headers = {Content-Type=[application/json;charset=UTF-8], Accept=[application/json;charset=UTF-8]}
               Body = {}
      Session Attrs = {}
  
  Handler:
               Type = com.codependent.customerspoc.api.controller.CustomerRestController
             Method = public reactor.core.publisher.Mono&lt;com.codependent.customerspoc.dto.Customer&gt; com.codependent.customerspoc.api.controller.CustomerRestController.createCustomer(com.codependent.customerspoc.dto.Customer)
  
  Async:
      Async started = false
       Async result = null
  
  Resolved Exception:
               Type = org.springframework.web.bind.MethodArgumentNotValidException
  
  ModelAndView:
          View name = null
               View = null
              Model = null
  
  FlashMap:
         Attributes = null
  
  MockHttpServletResponse:
             Status = 400
      Error message = null
            Headers = {Content-Type=[application/json;charset=UTF-8]}
       Content type = application/json;charset=UTF-8
               Body = {"scanAvailable":true,"prefetch":-1}
      Forwarded URL = null
     Redirected URL = null
            Cookies = []
  
  java.lang.IllegalStateException: The asyncDispatch CountDownLatch was not set by the TestDispatcherServlet.
  
      at org.springframework.util.Assert.state(Assert.java:73)
      at org.springframework.test.web.servlet.DefaultMvcResult.awaitAsyncDispatch(DefaultMvcResult.java:158)
      at org.springframework.test.web.servlet.DefaultMvcResult.getAsyncResult(DefaultMvcResult.java:145)
      at org.springframework.test.web.servlet.DefaultMvcResult.getAsyncResult(DefaultMvcResult.java:136)
      at org.springframework.test.web.servlet.request.MockMvcRequestBuilders.asyncDispatch(MockMvcRequestBuilders.java:269)
      at com.codependent.customerspoc.api.controller.CustomerRestControllerTests.shouldFailCreateNewCustomerValidation(CustomerRestControllerTests.java:180)
      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.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:532)
      at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:115)
      at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$6(TestMethodTestDescriptor.java:171)
      at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:72)
      at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:167)
      at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:114)
      at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:59)
      at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$4(NodeTestTask.java:108)
      at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:72)
      at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:98)
      at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:74)
      at java.util.ArrayList.forEach(ArrayList.java:1257)
      at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
      at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$4(NodeTestTask.java:112)
      at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:72)
      at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:98)
      at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:74)
      at java.util.ArrayList.forEach(ArrayList.java:1257)
      at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
      at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$4(NodeTestTask.java:112)
      at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:72)
      at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:98)
      at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:74)
      at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:32)
      at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57)
      at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:51)
      at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:220)
      at org.junit.platform.launcher.core.DefaultLauncher.lambda$execute$6(DefaultLauncher.java:188)
      at org.junit.platform.launcher.core.DefaultLauncher.withInterceptedStreams(DefaultLauncher.java:202)
      at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:181)
      at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:128)
      at com.intellij.junit5.JUnit5IdeaTestRunner.startRunnerWithArgs(JUnit5IdeaTestRunner.java:74)
      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)
  </code></pre>
  
  <p><strong>UPDATE:</strong> The problem seems to be related to the async error handler:</p>
  
  <pre><code>@ExceptionHandler
  @ResponseStatus(HttpStatus.BAD_REQUEST)
  public Flux&lt;ObjectError&gt; exception(MethodArgumentNotValidException ex) {
      logger.error("{}", ex.getLocalizedMessage(), ex);
      return Flux.fromIterable(ex.getBindingResult().getAllErrors());
  }
  </code></pre>
  
  <p>In this test:</p>
  
  <pre><code>MvcResult asyncResult = mockMvc
              .perform(post("/api/v1/customers")
                      .content("{}")
                      .accept(MediaType.APPLICATION_JSON_UTF8)
                      .contentType(MediaType.APPLICATION_JSON_UTF8))
              .andReturn();
  
      final String contentAsString = asyncResult.getResponse().getContentAsString();
  
      asyncResult.getAsyncResult();
  </code></pre>
  
  <p>contentAsString is <code>{"scanAvailable":true,"prefetch":-1}</code> and asyncResult.getAsyncResult() throws the mentioned exception.</p>