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

