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

Spring Boot中如何Mock WebClient编写Java单元测试?

解决WebClient单元测试空指针异常并模拟返回结果

核心思路

单元测试时WebClient.Builder未被容器正常注入,导致调用webClient.build()触发空指针。解决方案是通过MockBean注入模拟的WebClient.Builder,并逐步mock整个请求调用链,返回预设的模拟数据,完全避免调用真实接口。

具体实现步骤

1. 测试类基础配置

纯单元测试用@ExtendWith(MockitoExtension.class),Spring Boot集成测试用@SpringBootTest;用@Mock/@MockBean创建WebClient.Builder的模拟对象,用@InjectMocks/@Autowired注入被测试类。

2. Mock完整调用链

依次mockWebClient.Builder.build()、WebClient.get()、uri()、header()、retrieve()、onStatus()、bodyToMono()方法,最终返回包含模拟数据的Mono对象。

3. 编写测试用例

调用refreshData()方法,验证返回结果是否符合预期,并确认mock对象的调用次数是否正确。

完整代码示例

假设被测试类名为HumanDataService:

修正后的被测试类

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;
import java.util.ArrayList;
import java.util.List;

public class HumanDataService {

    @Autowired
    private WebClient.Builder webClientBuilder;
    private String token = "test-auth-token";
    private List<HumanDto> hdata = new ArrayList<>();

    public List<HumanDto> refreshData() {
        List<HumanDto> hdata2 = getAllHuman();
        if (!hdata.isEmpty()) {
            hdata.clear();
        }
        hdata.addAll(hdata2);
        return hdata;
    }

    private List<HumanDto> getAllHuman() {
        HumanLookup response = webClientBuilder.build()
                .get()
                .uri("https://user.asp/web")
                .header("Authorization", "Bearer " + token)
                .retrieve()
                .onStatus(HttpStatus::isError, clientResponse -> Mono.error(new RuntimeException("请求失败")))
                .bodyToMono(HumanLookup.class)
                .block();
        return response != null ? response.getUserInfo() : new ArrayList<>();
    }
}

// 配套DTO类
class HumanDto {}
class HumanLookup {
    private List<HumanDto> userInfo;
    public List<HumanDto> getUserInfo() { return userInfo; }
    public void setUserInfo(List<HumanDto> userInfo) { this.userInfo = userInfo; }
}

单元测试类

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;
import java.util.List;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.*;
import static org.junit.jupiter.api.Assertions.*;

@ExtendWith(MockitoExtension.class)
class HumanDataServiceTest {

    @Mock
    private WebClient.Builder webClientBuilder;
    @Mock
    private WebClient webClient;
    @Mock
    private WebClient.RequestHeadersUriSpec<?> requestHeadersUriSpec;
    @Mock
    private WebClient.RequestHeadersSpec<?> requestHeadersSpec;
    @Mock
    private WebClient.ResponseSpec responseSpec;

    @InjectMocks
    private HumanDataService humanDataService;

    @Test
    void refreshData_shouldReturnMockedHumanData() {
        // 准备模拟数据
        HumanDto mockDto = new HumanDto();
        List<HumanDto> mockDtoList = List.of(mockDto);
        HumanLookup mockLookup = new HumanLookup();
        mockLookup.setUserInfo(mockDtoList);

        // Mock调用链
        when(webClientBuilder.build()).thenReturn(webClient);
        when(webClient.get()).thenReturn((WebClient.RequestHeadersUriSpec) requestHeadersUriSpec);
        when(requestHeadersUriSpec.uri(anyString())).thenReturn(requestHeadersSpec);
        when(requestHeadersSpec.header(anyString(), anyString())).thenReturn(requestHeadersSpec);
        when(requestHeadersSpec.retrieve()).thenReturn(responseSpec);
        when(responseSpec.onStatus(any(), any())).thenReturn(responseSpec);
        when(responseSpec.bodyToMono(HumanLookup.class)).thenReturn(Mono.just(mockLookup));

        // 执行测试并验证
        List<HumanDto> result = humanDataService.refreshData();
        assertNotNull(result);
        assertEquals(1, result.size());

        // 验证mock调用次数
        verify(webClientBuilder, times(1)).build();
        verify(webClient, times(1)).get();
    }
}

关键说明

  • 若用Spring Boot集成测试,可替换@ExtendWith(MockitoExtension.class)为@SpringBootTest,用@MockBean替代@Mock,自动替换容器中的WebClient.Builder。
  • 原代码存在语法问题(如方法调用漏括号、RuntimeException拼写错误、Bearer后无空格),已同步修正,避免额外异常。
  • 在getAllHuman()中增加了response != null判断,防止模拟数据为空时触发二次空指针。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.16 17:55:20