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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.14 10:05:54