spring 如何对使用ZeroCopyHttpOutputMessage的终结点执行集成测试

hs1ihplo  于 2023-01-08  发布在  Spring
关注(0)|答案(1)|浏览(308)

我有一个端点将org.springframework.http.server.reactive.ServerHttpResponse转换为org.springframework.http.ZeroCopyHttpOutputMessage

@SneakyThrows
@GetMapping("/document/{documentId}")
public Mono<Void> serveDocument(@PathVariable final String documentId, final ServerHttpResponse response) {
    final Path documentLocation = fileManipulatorService.newFile(stagingConfigurationProperties.location(), documentId);

    return ((ZeroCopyHttpOutputMessage) response)
            .writeWith(documentLocation, 0, fileManipulatorService.size(documentLocation))
            .then(deleteIfExists(documentLocation));
}

通常情况下,这可以很好地工作,但是当使用org.springframework.test.web.reactive.server.WebTestClient调用端点时,调用会失败,并出现以下异常:

2022-12-30T18:49:07.678+01:00 ERROR 1392 --- [     parallel-1] a.w.r.e.AbstractErrorWebExceptionHandler : [1848ca22]  500 Server Error for HTTP GET "/document/11c92bad-6fe4-4c85-9d54-4bf4bbad3581"

java.lang.ClassCastException: class org.springframework.mock.http.server.reactive.MockServerHttpResponse cannot be cast to class org.springframework.http.ZeroCopyHttpOutputMessage (org.springframework.mock.http.server.reactive.MockServerHttpResponse and org.springframework.http.ZeroCopyHttpOutputMessage are in unnamed module of loader 'app')
    at com.github.bottomlessarchive.loa.stage.view.document.controller.StageDocumentController.serveDocument(StageDocumentController.java:53) ~[main/:na]
    Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException: 
Error has been observed at the following site(s):
    *__checkpoint ? HTTP GET "/document/11c92bad-6fe4-4c85-9d54-4bf4bbad3581" [ExceptionHandlingWebHandler]

这是我的测试结果:

@Test
void testServeDocument() {
    final UUID documentId = UUID.randomUUID();
    final byte[] content = {1, 2, 3, 4};

    final Path contentPath = setupFakeFile("/stage/" + documentId, content);
    when(fileManipulatorService.newFile("/stage/", documentId.toString()))
            .thenReturn(contentPath);

    final byte[] responseBody = webTestClient.get()
            .uri("/document/" + documentId)
            .exchange()
            .expectStatus()
            .isOk()
            .expectBody()
            .returnResult()
            .getResponseBody();

    assertThat(responseBody)
            .isEqualTo(content);
    assertThat(contentPath)
            .doesNotExist();
}

对我来说,一切似乎都是正确的。MockServerHttpResponse不扩展ZeroCopyHttpOutputMessage有什么原因吗?因为这个原因,我想向Sping Boot 提交一个bug报告,但在这样做之前,我得出结论,首先询问Stackoverflow可能是一个更好的主意。

ni65a41a

ni65a41a1#

首先,MockServerHttpResponse是测试响应的通用模拟实现,没有实际的服务器,因此它的实现方式足以方便测试。
其次,看起来没有任何保证ServerWebExchange中的响应必须实现ZeroCopyHttpOutputMessage,所以我不会在没有预先类型检查的情况下盲目地强制转换它。
另一个警告是,在Netty上,即使响应是ZeroCopyHttpOutputMessage,只有当指定的路径解析为本地文件系统File时,传输才会使用零字节拷贝,并且压缩和SSL/TLS未启用,否则将使用分块读/写。
( https://projectreactor.io/docs/netty/release/api/reactor/netty/NettyOutbound.html#sendFile-java.nio.file.Path-long-long- ).
考虑到所有这些,我将把你的控制器重构成这样:

@SneakyThrows
@GetMapping("/document/{documentId}")
public Mono<Void> serveDocument(@PathVariable final String documentId, final ServerHttpResponse response) {
    ...
    if (response instanceof ZeroCopyHttpOutputMessage zeroCopyHttpOutputMessage) {
        return zeroCopyHttpOutputMessage
                .writeWith(documentLocation, 0, ...)
                ...
    }
    return response
            .writeWith(DataBufferUtils.read(documentLocation, response.bufferFactory(), bufferSize))
            ...
}

要在集成测试中测试此控制器的ZeroCopyHttpOutputMessage部分,您可以使用真实(非模拟)Web环境,并将WebTestClient绑定到该环境,如下所示:

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class IntegrationTests {
    @LocalServerPort
    private Integer serverPort;

    ...

    @Test
    void testServeDocument() {
        WebTestClient webTestClient = WebTestClient
                .bindToServer()
                .baseUrl("http://localhost:" + serverPort)
                .build();
        
        ...

相关问题