You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SpringBoot WebTestClient测试无限SSE流时阻塞问题

问题描述

我实现了一个SSE控制器,支持按需向客户端发送事件,连接会永久保持(从不调用ResponseBodyEmitter#complete),功能运行正常,但编写测试用例时遇到问题:测试卡在WebTestClient.RequestHeadersSpec#exchange方法,原因是流是无限流。如果在发送第一条消息后调用ResponseBodyEmitter#complete,测试就能正常运行。

我怀疑这和应用底层用MVC而非WebFlux有关,但官方文档说可以用WebTestClient测试MVC应用,理论上应该能正常测试才对?

控制器代码

@RestController
@RequestMapping("/events")
public class EventsController {

    @GetMapping(produces = TEXT_EVENT_STREAM_VALUE)
    public SseEmitter subscribeToEvents() {
        SseEmitter sseEmitter = new SseEmitter(-1L);
        try {
            sseEmitter.send("Welcome!");

            // sseEmitter.complete(); // 取消注释后测试正常
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        return sseEmitter;
    }
}

测试类代码

@WebMvcTest(EventsController.class)
class EventsControllerTest {

    @Test
    void test(@Autowired WebTestClient webClient) {

        Flux<String> eventFlux = webClient.get().uri("/events")
                .accept(TEXT_EVENT_STREAM)
                .exchange()
                .expectStatus().isOk()
                .returnResult(String.class)
                .getResponseBody();

        StepVerifier.create(eventFlux)
                .expectNextMatches(e -> e.equals("Welcome!"))
                .thenCancel()
                .verify();
    }
}

使用Spring Boot 3.2.4版本,遵循官方文档实现。


解决方案

问题核心在于:WebTestClient测试MVC应用时,默认会等待响应完全结束(即触发complete)才会继续执行后续逻辑,而你的无限SSE流永远不会触发这个信号,导致exchange方法一直阻塞。

下面提供两种可行的解决方式:

方式一:用exchangeToFlux直接订阅流

跳过等待响应完成的逻辑,直接订阅响应的Flux流,一旦有数据就触发验证:

@WebMvcTest(EventsController.class)
class EventsControllerTest {

    @Test
    void test(@Autowired WebTestClient webClient) {
        webClient.get().uri("/events")
                .accept(TEXT_EVENT_STREAM)
                .exchangeToFlux(response -> {
                    response.expectStatus().isOk();
                    return response.bodyToFlux(String.class);
                })
                .as(StepVerifier::create)
                .expectNext("Welcome!")
                .thenCancel()
                .verify();
    }
}

方式二:配置WebTestClient响应超时

设置较短的响应超时时间,让WebTestClient在收到第一条消息后,即使流未完成也会结束等待,继续执行验证:

@WebMvcTest(EventsController.class)
class EventsControllerTest {

    @Autowired
    private MockMvc mockMvc;

    private WebTestClient webClient;

    @BeforeEach
    void setUp() {
        webClient = WebTestClient.bindToMockMvc(mockMvc)
                .responseTimeout(Duration.ofSeconds(1)) // 设置响应超时
                .build();
    }

    @Test
    void test() {
        Flux<String> eventFlux = webClient.get().uri("/events")
                .accept(TEXT_EVENT_STREAM)
                .exchange()
                .expectStatus().isOk()
                .returnResult(String.class)
                .getResponseBody();

        StepVerifier.create(eventFlux)
                .expectNext("Welcome!")
                .thenCancel()
                .verify(Duration.ofSeconds(2)); // 验证超时需大于响应超时
    }
}

原理说明

WebTestClient测试MVC应用时,底层依赖MockMvc处理请求。MockMvc默认会等待请求处理流程完全结束(即响应被标记为complete)才会返回结果。而你的无限SSE流永远不会触发complete,所以exchange方法会一直阻塞。

exchangeToFlux可以直接订阅响应的Flux流,只要有数据产生就会触发后续验证,无需等待整个响应完成;设置响应超时则是让WebTestClient在指定时间内没有新数据时,主动结束等待,继续执行验证逻辑。

内容的提问来源于stack exchange,提问作者Fabio Franco

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 21:32:56