Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MockMvc - IllegalStateException: Async result for handler was not set during specified timeToWait=-1 [SPR-16869] #21408

Closed
spring-projects-issues opened this issue May 23, 2018 · 5 comments
Assignees
Labels
in: test Issues in the test module in: web Issues in web modules (web, webmvc, webflux, websocket) type: bug A general bug
Milestone

Comments

@spring-projects-issues
Copy link
Collaborator

Adrian S opened SPR-16869 and commented

When trying to test Server-Sent Events endpoint created using rxjava2 and spring's ReactiveTypeHandler you encounter:

java.lang.IllegalStateException: Async result for handler [io.reactivex.Flowable<java.lang.String> com.example.asyncssebug.MockMvcAsyncBugTest$TestApp.sse()] was not set during the specified timeToWait=-1java.lang.IllegalStateException: Async result for handler [io.reactivex.Flowable<java.lang.String> com.example.asyncssebug.MockMvcAsyncBugTest$TestApp.sse()] was not set during the specified timeToWait=-1
 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)

Example test code to reproduce bug along with walkaround:

package com.example.asyncssebug;

import io.reactivex.Flowable;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.awt.*;
import java.util.concurrent.TimeUnit;

import static org.hamcrest.Matchers.nullValue;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.asyncDispatch;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.request;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@RunWith(SpringRunner.class)
@SpringBootTest(classes = MockMvcAsyncBugTest.TestApp.class)
@AutoConfigureMockMvc
public class MockMvcAsyncBugTest {

    @RestController
    @RequestMapping("/events")
    @SpringBootApplication
    public static class TestApp {

        @GetMapping(produces = MediaType.TEXT_EVENT_STREAM_VALUE)
        Flowable<String> sse() {
            return Flowable.intervalRange(0, 3, 0, 1, TimeUnit.SECONDS)
                    .map(aLong -> String.format("event%d", aLong));
        }
    }

    @Autowired
    MockMvc mockMvc;

    @Test
    public void failsWithIllegalStateExceptionAsyncResultForHandlerWasNotSetDuringSpecifiedTimeToWait() throws Exception {
        MvcResult mvcResult = mockMvc.perform(get("/events"))
                .andExpect(request().asyncStarted())
                .andExpect(status().isOk())
                .andReturn();

        mockMvc.perform(asyncDispatch(mvcResult))
                .andExpect(content().string("data:event0\n\ndata:event1\n\ndata:event2\n\n"));
    }

    @Test
    public void alsoFailsWithIllegalStateExceptionAsyncResultForHandlerWasNotSetDuringSpecifiedTimeToWait() throws Exception {
        mockMvc.perform(get("/events"))
                .andExpect(request().asyncStarted())
                .andExpect(request().asyncResult(nullValue()))
                .andExpect(status().isOk())
                .andExpect(content().string("data:event0\n\ndata:event1\n\ndata:event2\n\n"))
                .andReturn();
    }

    @Test
    public void walkaroundToMakeItWork() throws Exception {
        MvcResult mvcResult = mockMvc.perform(get("/events"))
                .andExpect(request().asyncStarted())
                .andExpect(status().isOk())
                .andReturn();
        mvcResult.getAsyncResult(5000L); // walkaround
        mockMvc.perform(asyncDispatch(mvcResult))
                .andExpect(content().string("data:event0\n\ndata:event1\n\ndata:event2\n\n"));
    }
}

Spring Boot version used is 2.0.2.RELEASE

Seems like default getAsyncResult(-1) instead of waiting forever doesn't wait at all. As a walkaround you can add mvcResult.getAsyncResult(5000L) and then perform asyncDispatch on the mvcResult

 


Affects: 5.0.6

Referenced from: commits 2a993bf, 9d36fd0

@spring-projects-issues
Copy link
Collaborator Author

Rossen Stoyanchev commented

From the Javadoc of MvcResult#getAsyncResult:

* @param timeToWait how long to wait for the async result to be set, in
* 	milliseconds; if -1, then the async request timeout value is used,
*  i.e.{@link org.springframework.mock.web.MockAsyncContext#getTimeout()}.

So -1 in this context actually means fallback on the timeout value of the async request. That's typically something like 10 seconds (e.g. on Tomcat), as well as in our MockAsyncContext, and it can be customized globally (via WebMvcConfigurer. However in the case of SSE the async request is explicitly set to -1, which in the Servlet API does mean wait forever, but then DefaultMvcResult needs to translate such a fallback value into a blocking call.

@spring-projects-issues spring-projects-issues added type: bug A general bug in: test Issues in the test module in: web Issues in web modules (web, webmvc, webflux, websocket) labels Jan 11, 2019
@spring-projects-issues spring-projects-issues added this to the 5.0.7 milestone Jan 11, 2019
@hantsy
Copy link
Contributor

hantsy commented Sep 5, 2021

I encountered the same issue when writing test against SSE endpoints using MockMvc, even added the workaround in the original post, it still failed.

java.lang.IllegalStateException: Async result for handler [com.example.demo.SseController#sseMessages()] was not set during the specified timeToWait=5000

	at org.springframework.test.web.servlet.DefaultMvcResult.getAsyncResult(DefaultMvcResult.java:146)
	at com.example.demo.SseControllerTests.testSseEndpoints(SseControllerTests.java:61)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:568)
	at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:688)
	at org.junit.jupiter.engine.execution.MethodInvocation.proceed(MethodInvocation.java:60)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain$ValidatingInvocation.proceed(InvocationInterceptorChain.java:131)
	at org.junit.jupiter.engine.extension.TimeoutExtension.intercept(TimeoutExtension.java:149)
	at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestableMethod(TimeoutExtension.java:140)
	at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestMethod(TimeoutExtension.java:84)
	at org.junit.jupiter.engine.execution.ExecutableInvoker$ReflectiveInterceptorCall.lambda$ofVoidMethod$0(ExecutableInvoker.java:115)
	at org.junit.jupiter.engine.execution.ExecutableInvoker.lambda$invoke$0(ExecutableInvoker.java:105)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptedInvocation.proceed(InvocationInterceptorChain.java:106)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain.proceed(InvocationInterceptorChain.java:64)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain.chainAndInvoke(InvocationInterceptorChain.java:45)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain.invoke(InvocationInterceptorChain.java:37)
	at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:104)
	at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:98)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$6(TestMethodTestDescriptor.java:210)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:206)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:131)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:65)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:139)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:129)
	at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:127)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:126)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:84)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
	at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:143)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:129)
	at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:127)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:126)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:84)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
	at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:143)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:129)
	at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:127)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:126)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:84)
	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.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:108)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:88)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.lambda$execute$0(EngineExecutionOrchestrator.java:54)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.withInterceptedStreams(EngineExecutionOrchestrator.java:67)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:52)
	at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:96)
	at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:75)
	at com.intellij.junit5.JUnit5IdeaTestRunner.startRunnerWithArgs(JUnit5IdeaTestRunner.java:71)
	at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33)
	at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:235)
	at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:54)

@rstoyanchev
Copy link
Contributor

MockMvc is not a good fit for testing streaming responses. There is a section on Streaming Responses in the docs for MockMvc. Note that WebTestClient can be set up as a test client against MockMvc.

@hantsy
Copy link
Contributor

hantsy commented Sep 6, 2021

I have an example project written with WebMvc SseEmitter.

When switching to use MockMvcWebTestCient in tests, I still got the same exception.

org.springframework.web.reactive.function.client.WebClientRequestException: Async result for handler 
[com.example.demo.SseController#sseMessages()] was not set during the specified timeToWait=1000; nested exception is 
java.lang.IllegalStateException: Async result for handler [com.example.demo.SseController#sseMessages()] was not set during 
the specified timeToWait=1000

I have to add sseEmitter.complete in the controller to make the MockMvc and MockMvcWebTestClient work, else it will be blocked till it is timeout, and threw the exceptions.

@rstoyanchev
Copy link
Contributor

The idea is you would use WebTestClient as the API for all tests but for SSE tests it would be against a live server, i.e. WebTestClient.bindToServer.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: test Issues in the test module in: web Issues in web modules (web, webmvc, webflux, websocket) type: bug A general bug
Projects
None yet
Development

No branches or pull requests

3 participants