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

Spring WebClient Mockito测试POST请求时body()空指针问题

WebClient Mock测试中body()方法返回NullPointerException的解决方法

问题现象

测试时抛出空指针异常:

java.lang.NullPointerException: Cannot invoke "org.springframework.web.reactive.function.client.WebClient$RequestHeadersSpec.retrieve()" because the return value of "org.springframework.web.reactive.function.client.WebClient$RequestBodySpec.body(org.reactivestreams.Publisher, java.lang.Class)" is null

业务代码

import com.bluelagoon.payetonkawa.dolibarr.entities.input.LoginEntity;
import com.bluelagoon.payetonkawa.dolibarr.entities.output.SuccessEntity;
import com.bluelagoon.payetonkawa.dolibarr.entities.output.UserInfoEntity;
import com.bluelagoon.payetonkawa.dolibarr.services.DolibarrInfraService;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;

@Service
public class DolibarrInfraServiceImpl implements DolibarrInfraService {

    private final WebClient webClient;

    public DolibarrInfraServiceImpl(WebClient webClient) {
        this.webClient = webClient;
    }

    @Override
    public SuccessEntity login(LoginEntity login) {
        return webClient.post()
               .uri("/login")
               .body(Mono.just(login), LoginEntity.class)
               .retrieve()
               .bodyToMono(SuccessEntity.class)
               .block();
    }

   ...
}

测试代码

package com.bluelagoon.payetonkawa.dolibarr.impl;

import com.bluelagoon.payetonkawa.dolibarr.entities.input.LoginEntity;
import com.bluelagoon.payetonkawa.dolibarr.entities.output.SuccessEntity;
import com.bluelagoon.payetonkawa.dolibarr.entities.output.TokenEntity;
import com.bluelagoon.payetonkawa.dolibarr.services.DolibarrInfraService;
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.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.BodyInserter;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;

import java.net.http.HttpRequest;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.lenient;

@ExtendWith(MockitoExtension.class)
class DolibarrInfraServiceImplTest {

    @Mock
    private 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;

    @InjectMocks
    private DolibarrInfraServiceImpl dolibarrInfraService;

    @Test
    void loginTest_should_return_a_valid_SuccessEntity(){
        var loginEntity = new LoginEntity();
        loginEntity.setLogin("test");
        loginEntity.setPassword("test");

        var tokenEntity = new TokenEntity();
        tokenEntity.setCode(200);
        tokenEntity.setToken("test");

        var successEntity = new SuccessEntity();
        successEntity.setSuccess(tokenEntity);

        when(webClientMock.post())
               .thenReturn(requestBodyUriSpecMock);

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

        when(requestBodySpecMock.body(Mono.just(loginEntity), LoginEntity.class))
               .thenReturn(requestHeadersSpecMock);

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

        when(responseSpecMock.bodyToMono(ArgumentMatchers.<Class<SuccessEntity>>notNull()))
               .thenReturn(Mono.just(successEntity));


        var expectedTokenEntity = new TokenEntity();
        expectedTokenEntity.setToken("test");
        expectedTokenEntity.setCode(200);

        var expectedSuccessEntity = new SuccessEntity();
        expectedSuccessEntity.setSuccess(expectedTokenEntity);

        var resultSuccessEntity = dolibarrInfraService.login(loginEntity);

        assertEquals(expectedSuccessEntity, resultSuccessEntity);
    }
}

问题原因

测试代码中使用具体的Mono实例(Mono.just(loginEntity))作为body()方法的匹配参数,但业务代码中创建的Mono.just(login)是全新的实例,两者不是同一个对象,导致Mockito找不到对应的存根配置,返回null进而触发空指针异常。

解决方案

将body()方法的参数匹配改为通用匹配器,替换为any(Mono.class)和any(Class.class),修改后的存根代码如下:

when(requestBodySpecMock.body(any(Mono.class), any(Class.class)))
       .thenReturn(requestHeadersSpecMock);

修改后的完整测试方法

@Test
void loginTest_should_return_a_valid_SuccessEntity(){
    var loginEntity = new LoginEntity();
    loginEntity.setLogin("test");
    loginEntity.setPassword("test");

    var tokenEntity = new TokenEntity();
    tokenEntity.setCode(200);
    tokenEntity.setToken("test");

    var successEntity = new SuccessEntity();
    successEntity.setSuccess(tokenEntity);

    when(webClientMock.post())
           .thenReturn(requestBodyUriSpecMock);

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

    // 替换为通用参数匹配器
    when(requestBodySpecMock.body(any(Mono.class), any(Class.class)))
           .thenReturn(requestHeadersSpecMock);

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

    when(responseSpecMock.bodyToMono(ArgumentMatchers.<Class<SuccessEntity>>notNull()))
           .thenReturn(Mono.just(successEntity));


    var expectedTokenEntity = new TokenEntity();
    expectedTokenEntity.setToken("test");
    expectedTokenEntity.setCode(200);

    var expectedSuccessEntity = new SuccessEntity();
    expectedSuccessEntity.setSuccess(expectedTokenEntity);

    var resultSuccessEntity = dolibarrInfraService.login(loginEntity);

    assertEquals(expectedSuccessEntity, resultSuccessEntity);
}

可选:精确匹配(需额外配置)

如果需要对请求体内容做精确匹配,需确保LoginEntity正确实现equals()和hashCode()方法,再使用eq()匹配器:

when(requestBodySpecMock.body(eq(Mono.just(loginEntity)), eq(LoginEntity.class)))
       .thenReturn(requestHeadersSpecMock);

内容的提问来源于stack exchange,提问作者Tokoro-San

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.27 10:12:53