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

Mock POST类型WebClient时触发NullPointerException异常排查

WebClient单元测试NullPointerException异常排查

java.lang.NullPointerException: 无法调用"org.springframework.web.reactive.function.client.WebClient$RequestBodySpec.body(org.reactivestreams.Publisher, java.lang.Class)",因为"org.springframework.web.reactive.function.client.WebClient$RequestBodySpec.headers(java.util.function.Consumer)"的返回值为null

相关Controller代码

package com.bezkoder.springjwt.controllers;

import com.bezkoder.springjwt.models.Product;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.util.UriComponentsBuilder;
import reactor.core.publisher.Mono;

import java.util.Arrays;
import java.util.function.Consumer;

@RestController
public class ProductClientController {

    @Autowired
    WebClient webClient;

    @GetMapping("/catproducts")
    public String callSaveProductApi() {
        String response = null;
        Product product = new Product();
        product.setId(1l);
        product.setName("kumar2");
        product.setPrice(88);
        try {
            response = webClient.post()
                    .uri(UriComponentsBuilder.fromUriString("http://localhost:8080/products").build().toString())
                    .headers(getHttpHeadersConsumer()).body(Mono.just(product), Product.class)
                    .retrieve().bodyToMono(String.class).block();

        } catch (Exception e) {
            System.out.println(e);
        }
        return response;

    }

    private Consumer<HttpHeaders> getHttpHeadersConsumer() {
        return httpHeaders -> {
            httpHeaders.setContentType(MediaType.APPLICATION_JSON);
            httpHeaders.setAccept(Arrays.asList(MediaType.APPLICATION_JSON));
        };
    }

}

相关测试类代码

package com.bezkoder.springjwt.controllers;

import com.bezkoder.springjwt.models.Product;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentMatchers;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.lenient;
import static org.mockito.Mockito.when;

@ExtendWith(MockitoExtension.class)
public class ProductClientControllerTest {

    @InjectMocks
    ProductClientController productClientController;

    @Mock
    WebClient webClientMock;

    @Mock
    private WebClient.RequestBodyUriSpec requestBodyUriSpecMock;

    @Mock
    private WebClient.RequestBodySpec requestBodySpecMock;

    @Mock
    private WebClient.RequestHeadersUriSpec requestHeadersUriSpecMock;

    @Mock
    private WebClient.RequestHeadersSpec requestHeadersSpecMock;

    @Mock
    private WebClient.ResponseSpec responseSpecMock;
    
    @Test
    void callSaveProductApi(){
        Product product = new Product();
        product.setId(1l);
        product.setName("kumar2");
        product.setPrice(88);
        when(webClientMock.post())
                .thenReturn(requestBodyUriSpecMock);

        when(requestBodyUriSpecMock.uri(anyString()))
                .thenReturn(requestBodySpecMock);

        when(requestBodySpecMock.body(Mono.just(product), Product.class))
                .thenReturn(requestHeadersSpecMock);
        when(requestBodyUriSpecMock.headers(any())).thenReturn(requestBodySpecMock);

        when(requestHeadersSpecMock.retrieve())
                .thenReturn(responseSpecMock);

        when(responseSpecMock.bodyToMono(ArgumentMatchers.<Class<String>>notNull()))
                .thenReturn(Mono.just("success"));

        String s = productClientController.callSaveProductApi();
    }
     
}

异常原因

  1. Mock调用链不匹配:Controller中调用顺序是webClient.post() -> uri() -> headers() -> body(),其中headers()是调用uri()返回的RequestBodySpec对象的方法,但测试中仅Mock了requestBodyUriSpecMock.headers(),未MockrequestBodySpecMock.headers(),导致实际调用时headers()返回null,后续调用body()触发空指针。
  2. 参数匹配过于严格:测试中body()方法用Mono.just(product)作为参数匹配,但Controller内部创建了新的Product实例,两者不是同一个对象,导致Mock的body()方法不生效,也会引发调用链中断。

解决方法

步骤1:修正RequestBodySpec.headers()的Mock

添加对requestBodySpecMock.headers()的Mock,让它返回自身以维持链式调用:

when(requestBodySpecMock.headers(any(Consumer.class))).thenReturn(requestBodySpecMock);

步骤2:放宽body()方法的参数匹配

使用any(Mono.class)和eq(Product.class)来匹配任意Mono实例和指定的类,避免因对象实例不同导致Mock失效:

// 需要导入 org.mockito.ArgumentMatchers.eq
when(requestBodySpecMock.body(any(Mono.class), eq(Product.class))).thenReturn(requestHeadersSpecMock);

完整修正后的测试方法

@Test
void callSaveProductApi(){
    Product product = new Product();
    product.setId(1l);
    product.setName("kumar2");
    product.setPrice(88);
    
    when(webClientMock.post()).thenReturn(requestBodyUriSpecMock);
    when(requestBodyUriSpecMock.uri(anyString())).thenReturn(requestBodySpecMock);
    
    // 关键:Mock RequestBodySpec的headers方法返回自身
    when(requestBodySpecMock.headers(any(Consumer.class))).thenReturn(requestBodySpecMock);
    
    // 放宽参数匹配
    when(requestBodySpecMock.body(any(Mono.class), eq(Product.class))).thenReturn(requestHeadersSpecMock);
    
    when(requestHeadersSpecMock.retrieve()).thenReturn(responseSpecMock);
    when(responseSpecMock.bodyToMono(ArgumentMatchers.<Class<String>>notNull())).thenReturn(Mono.just("success"));
    
    String s = productClientController.callSaveProductApi();
    // 可选:添加断言验证结果
    // assertEquals("success", s);
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.29 06:55:54