如何测试返回Mono<ResponseEntity<Void>>的Controller方法?
WebFlux Controller方法测试方案
针对你这个返回Mono<ResponseEntity<Void>>的接口,直接用Spring官方提供的WebTestClient测试即可,这是WebFlux栈专属的测试工具,原生支持响应式断言,不需要额外引入第三方依赖,常用的测试模式分两种:
单元测试(不启动完整Spring容器,执行速度最快)
这种模式只加载Controller本身,依赖的Service、Mapper全部用Mock对象替换,启动开销极小,适合做接口逻辑快速验证。
核心是把WebTestClient直接绑定到待测试的Controller实例上,不需要启动真实的Web服务器。
示例代码:
@ExtendWith(MockitoExtension.class) class YourControllerTest { private YourController controller; @Mock private YourService targetService; private WebTestClient testClient; @BeforeEach void init() { // 手动初始化Controller,注入Mock的Service依赖 controller = new YourController(targetService); // 绑定测试客户端到当前Controller实例 testClient = WebTestClient.bindToController(controller).build(); } @Test void shouldReturn200WhenCallFunctionName() { // 构造测试入参 Request testReq = new Request(); // 自行填充testReq的业务字段,比如 testReq.setBizId(1001L); // 给Service方法打桩,定义Mock调用逻辑 doNothing().when(targetService).functionName(any(RequestDto.class)); // 发起模拟请求、执行结果断言 testClient.post() // 替换成你接口实际的HTTP方法,@GetMapping就换成get() .uri("/your/actual/api/path") // 替换成你接口实际配置的访问路径 .bodyValue(testReq) .exchange() // 断言返回状态码为200 OK .expectStatus().isOk() // 断言返回体为空 .expectBody(Void.class); // 验证Service层方法被正确调用,且入参Dto符合映射规则 ArgumentCaptor<RequestDto> dtoCaptor = ArgumentCaptor.forClass(RequestDto.class); verify(targetService, times(1)).functionName(dtoCaptor.capture()); RequestDto capturedParam = dtoCaptor.getValue(); // 自行添加字段映射断言,比如 assertEquals(testReq.getBizId(), capturedParam.getBizId()); } }
集成测试(启动完整Spring上下文,适合全链路验证)
如果需要验证参数序列化、拦截器逻辑、全局异常处理等完整链路逻辑,可以启动完整Spring容器,用随机端口启动服务,直接注入WebTestClient发起请求即可:
// 随机端口启动服务,避免本地端口冲突 @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) class YourControllerIntegratedTest { @Autowired private WebTestClient testClient; // 用MockBean替换容器中真实的Service实例,避免真的调用下游依赖 @MockBean private YourService targetService; @Test void shouldReturn200WhenCallFunctionName() { Request testReq = new Request(); // 填充测试入参字段 doNothing().when(targetService).functionName(any(RequestDto.class)); testClient.post() .uri("/your/actual/api/path") .bodyValue(testReq) .exchange() .expectStatus().isOk() .expectBody(Void.class); verify(targetService, times(1)).functionName(any(RequestDto.class)); } }
注意:你贴的Controller代码存在响应式写法隐患:如果
service.functionName(Dto)返回的是Mono/Flux类型的响应式对象,你现在的写法没有把它加入响应式执行链路,会出现响应式上下文丢失、方法不执行、线程切换异常的问题。正确写法应该把Service调用放到响应式操作符链路里,参考:public Mono<ResponseEntity<Void>> functionName(final Request request) { RequestDto dto = RequestMapper.INSTANCE.toDto(request); return service.functionName(dto) .thenReturn(new ResponseEntity<>(HttpStatus.OK)); }如果你的Service方法是普通同步阻塞方法,建议加上
boundedElastic调度器调度,避免阻塞Netty的事件循环线程。
内容的提问来源于stack exchange,提问作者Swadib Patro
相关产品推荐
相关产品推荐

