SpringBoot响应式路由与处理器单元测试:解决null Mono返回异常
问题场景
在SpringBoot响应式项目中实现了函数式路由与处理器,Postman调用接口和集成测试均正常通过,但单元测试始终失败,抛出The mapper returned a null Mono错误。
相关代码实现
函数式路由
@Bean public RouterFunction<ServerResponse> greetingRoute(Utilities utils, AppConfig cfg, GreetingHandler greetingHandler) { return RouterFunctions.route() .path("/api",api -> api // x-headers validation .filter((request,next) -> utils.verifyXHeaders(request,cfg,next)) // Customer urls .nest(accept(MediaType.APPLICATION_JSON),ops -> ops // Greeting API .GET("/v2/hello",greetingHandler::hello) ) .build()) .build(); }
处理器函数
public Mono<ServerResponse> hello(ServerRequest serverRequest) { Mono<Greeting> greetingResponse = greetingSvc.greetingFunction(); return greetingResponse.flatMap(greeting -> { BaseResponse brs = BaseResponse.builder() .responseCode(String.valueOf(HttpStatus.OK.value())) .responseMsg("Greeting") .detailedMsg("Greeting Success") .responseData(greeting) .build(); return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON) .bodyValue(brs); }); }
单元测试代码
@Slf4j class GreetingHandlerTest { GreetingSvc greetingSvc; WebTestClient client; @BeforeEach void setUp(){ this.greetingSvc = mock(GreetingSvc.class); Utilities utils = mock(Utilities.class); AppConfig cfg = mock(AppConfig.class); Greeting greeting = new Greeting("Hello Spring"); GreetingHandler greetingHandler = new GreetingHandler(this.greetingSvc); when(this.greetingSvc.greetingFunction()).thenReturn(Mono.just(greeting)); RouterFunction<?> route = new AppRouter() .greetingRoute(utils,cfg,greetingHandler); this.client = WebTestClient.bindToRouterFunction(route) .build(); } @Test @DisplayName("Get Hello Back") void testHello() { client.get() .uri("/api/v2/hello") .headers(Utilities.getHttpHeaders()) .accept(MediaType.APPLICATION_JSON) .exchange() .expectStatus().isOk() .expectBody(BaseResponse.class); ; } }
错误堆栈信息
12:46:14.282 [parallel-1] DEBUG org.springframework.web.server.adapter.HttpWebHandlerAdapter - [73a1446b] HTTP GET "/api/v2/hello" 12:46:14.334 [parallel-1] ERROR org.springframework.web.server.adapter.HttpWebHandlerAdapter - [73a1446b] 500 Server Error for HTTP GET "/api/v2/hello" java.lang.NullPointerException: The mapper returned a null Mono at java.util.Objects.requireNonNull(Objects.java:228) Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException: Error has been observed at the following site(s): *__checkpoint ⇢ HTTP GET "/api/v2/hello" [ExceptionHandlingWebHandler] Original Stack Trace: at java.util.Objects.requireNonNull(Objects.java:228) at reactor.core.publisher.MonoFlatMap$FlatMapMain.onNext(MonoFlatMap.java:125) ... 省略后续堆栈信息
正常返回结果(Postman调用)
{ "responseCode": "200", "responseMsg": "Greeting", "detailedMsg": "Greeting Success", "Data": { "message": "Hello, Spring!" } }
GreetingSvc实现
public Mono<Greeting> greetingFunction(){ return Mono.just(new Greeting("Hello, Spring!")); }
问题分析
单元测试中使用Mockito mock了Utilities类,但未指定verifyXHeaders方法的返回值,该方法默认返回null。而路由中的filter依赖这个方法返回的Mono<ServerResponse>来继续处理请求链,Reactor框架要求Mono不能为null,因此触发了NullPointerException。
集成测试和Postman调用使用的是真实的Utilities实例,该方法会正常返回处理后的Mono,所以没有问题。
修复方案
在单元测试的setUp方法中,为mock的Utilities实例指定verifyXHeaders方法的返回值,让它继续传递请求到后续处理链:
修改后的setUp方法:
@BeforeEach void setUp(){ this.greetingSvc = mock(GreetingSvc.class); Utilities utils = mock(Utilities.class); AppConfig cfg = mock(AppConfig.class); Greeting greeting = new Greeting("Hello Spring"); // 指定verifyXHeaders的返回值,让请求继续执行后续处理链 when(utils.verifyXHeaders(any(ServerRequest.class), any(AppConfig.class), any(HandlerFunction.class))) .thenAnswer(invocation -> { HandlerFunction<?> next = invocation.getArgument(2); return next.handle(invocation.getArgument(0)); }); GreetingHandler greetingHandler = new GreetingHandler(this.greetingSvc); when(this.greetingSvc.greetingFunction()).thenReturn(Mono.just(greeting)); RouterFunction<?> route = new AppRouter() .greetingRoute(utils,cfg,greetingHandler); this.client = WebTestClient.bindToRouterFunction(route) .build(); }
如果Utilities类的verifyXHeaders方法可以直接调用真实实现(且不依赖外部资源),也可以用更简洁的写法:
when(utils.verifyXHeaders(any(), any(), any())).thenCallRealMethod();
验证
修改后重新运行单元测试,请求链能正常走到处理器函数,即可正常通过测试。
内容的提问来源于stack exchange,提问作者Cozy
相关产品推荐
相关产品推荐

