2

I have a simple Spring Boot service that uses StreamingResponseBody to write the response. When testing this with MockMvc, the first test gives me the correct response, but the second one has an empty response body. However, when I add a little sleep time to the test, they both work.

Here is my controller (Kotlin)

@RestController
class DummyController() {
    @GetMapping()
    fun streamIndex() =
        ResponseEntity.ok()
            .contentType(MediaType.TEXT_PLAIN)
            .body(StreamingResponseBody { it.write("Hello world".toByteArray()) })
}

And my tests:

@AutoConfigureMockMvc(print = MockMvcPrint.NONE)
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class DummyControllerTest {
    private val controller = DummyController()
    private val mvc = MockMvcBuilders.standaloneSetup(controller).setControllerAdvice(GlobalExceptionHandler()).build()

    @Test
    fun test1() {
        mvc.perform(get("/")).andExpect(status().isOk).andExpect(content().string("Hello world"))
    }

    @Test
    fun test2() {
        mvc.perform(get("/")).andExpect(status().isOk).andExpect(content().string("Hello world"))
    }
}

Thee first test succeeds, the second one fails with:

java.lang.AssertionError: Response content expected:<Hello world> but was:<>

Then when I add a little delay in the test it works:

@Test
fun test2() {
    mvc.perform(get("/")).andExpect(status().isOk)
        .andDo { Thread.sleep(5) }
        .andExpect(content().string("Hello world"))
}

Is this a bug in MockMvc or something missing in my test?

Thanks in advance,

Rob

Rob Gansevles
  • 143
  • 1
  • 10

2 Answers2

1

Instead of adding a delay, you will need to fetch the async result first by doing the following:

@Test
fun test2() {
    mvc.perform(get("/"))
        .andExpect(request().asyncStarted())
        .andDo(MvcResult::getAsyncResult)
        .andExpect(status().isOk)
        .andExpect(content().string("Hello world"))
}

Make sure you add this to both tests.

Source: https://github.com/spring-projects/spring-framework/blob/main/spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/AsyncTests.java @line 81

Bly
  • 11
  • 2
  • 1
    Alternatively they should be able to add a `SyncTaskExecutor` to their test configuration that would be used for tests only. – filpa Sep 12 '22 at 08:24
0

Your test is wrong/weird. You have @SpringBootTest which starts an app on the port, have configured @AutoConfigureMockMvc and totally ignore all of that.

The combination in your @SpringBootTest wouldn't even work as you cannot use RANDOM_PORT with MockMvc (that only works with a MOCK environment). You either use the MOCK environment or switch to TestRestTemplate/TestWebClient for testing.

You probably worked around the issues you had with manually constructing a controller and manually registering MockMvc with that controller.

Finally you are using an async request which you would need to consume properly as well. See async testing in the Spring Reference Guide. It also shows another way of testing using the WebTestClient which is preferred for streaming responses.

In short your test is a bit af a frankenstein test.

I would strongly suggest to use the technologies in a proper way and rewrite your test as follows.

@WebMvcTest(DummyController.class)
class DummyControllerTest {
    @Autowired
    private val mvc;
    
    @Test
    fun test1() {
       var mvcResult = mvc.perform(get("/"))
          .andExpect(request().asyncStarted())
          .andExpect(status().isOk)
          .andExpect(request().asyncResult("Hello world"))

    }

    @Test
    fun test2() {
        mvc.perform(get("/"))
           .andExpect(request().asyncStarted())
           .andExpect(status().isOk)
           .andExpect(request().asyncResult("Hello world"))
    }
}

This test won't start a full blown container but only the Web MVC stuff and the stuff needed for your controller. In the tests the async result is now also properly parsed and asserted. All in all it should now also run a lot faster due to the reduced things needed to start (unless there wasn't much to start with).

M. Deinum
  • 115,695
  • 22
  • 220
  • 224