使用 StreamingResponseBody 时 Spring MockMvc 第二次测试失败

发布于 2025-01-11 04:55:52 字数 1517 浏览 0 评论 0原文

我有一个简单的 Spring Boot 服务,它使用 StreamingResponseBody 来编写响应。 当使用 MockMvc 进行测试时,第一个测试给出了正确的响应,但第二个测试的响应主体为空。 然而,当我在测试中添加一点睡眠时间时,它们都会起作用。

这是我的控制器(Kotlin)

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

和我的测试:

@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"))
    }
}

第一个测试成功,第二个测试失败:

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

然后,当我在测试中添加一点延迟时,它会起作用:

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

这是 MockMvc 中的错误还是我的测试中缺少的东西?

预先感谢,

罗布

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

烂人 2025-01-18 04:55:52

您需要首先通过执行以下操作来获取异步结果,而不是添加延迟:

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

确保将其添加到两个测试中。

来源: 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

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

拥醉 2025-01-18 04:55:52

你的测试是错误的/奇怪的。您有 @SpringBootTest 在端口上启动应用程序,配置了 @AutoConfigureMockMvc 并完全忽略所有这些。

@SpringBootTest 中的组合甚至不起作用,因为您无法将 RANDOM_PORTMockMvc 一起使用(仅适用于 MOCK 环境)。您可以使用 MOCK 环境或切换到 TestRestTemplate/TestWebClient 进行测试。

您可能解决了手动构建控制器并向该控制器手动注册 MockMvc 时遇到的问题。

最后,您使用了一个异步请求,您也需要正确使用该请求。请参阅 异步Spring 参考指南中的测试。它还展示了使用 WebTestClient 进行测试的另一种方法,这是流响应的首选方法。

简而言之,你的测试有点像弗兰肯斯坦测试。

我强烈建议以正确的方式使用这些技术并按如下方式重写您的测试。

@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"))
    }
}

此测试不会启动完整的容器,而只会启动 Web MVC 内容和控制器所需的内容。在测试中,异步结果现在也已正确解析和断言。总而言之,由于启动所需的东西减少了(除非启动的东西不多),它现在应该运行得更快。

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).

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文