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-issuemaster opened this Issue May 23, 2018 · 1 comment

Comments

Projects
None yet
2 participants
@spring-issuemaster
Copy link
Collaborator

spring-issuemaster commented May 23, 2018

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-issuemaster

This comment has been minimized.

Copy link
Collaborator

spring-issuemaster commented May 24, 2018

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment