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

无法Mock WebClient:单元测试未拦截外部API调用求解决方案

问题原因

你当前的代码无法Mock WebClient的核心问题是:EntityService方法内直接创建了WebClient.Builder实例,没有通过依赖注入的方式引入,导致测试中mock的webClientBuilder和服务实际使用的不是同一个对象,自然无法拦截请求。

解决方案

1. 重构EntityService,注入WebClient.Builder

把WebClient.Builder改为构造注入,让测试可以传入mock实例:

import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;
import reactor.core.publisher.Flux;
import java.util.List;

public class EntityService {

    private final WebClient.Builder webClientBuilder;

    // 通过构造函数注入WebClient.Builder
    public EntityService(WebClient.Builder webClientBuilder) {
        this.webClientBuilder = webClientBuilder;
    }

    public Mono<List<EntityDTO>> getNumber(String uri) {
        return webClientBuilder
                .baseUrl(uri)
                .build()
                .get()
                .exchange()
                .flatMap(response -> response.bodyToMono(EntityDTO.class))
                .flatMapMany(dto -> Flux.fromIterable(dto.getEntityDetails()))
                .map(this::getEntityDTO)
                .collectList();
    }

    // 你的实体转换方法
    private EntityDTO getEntityDTO(EntityDetail detail) {
        // 此处为转换逻辑示例
        return new EntityDTO();
    }
}

2. 调整测试代码,正确Mock调用链

测试中需要mock WebClient.Builder的整个调用链,确保每个链式调用都返回对应的mock对象,同时正确处理Reactor类型的返回值:

import org.junit.jupiter.api.Test;
import org.mockito.Mock;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.client.ClientResponse;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.List;

import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.*;

public class EntityServiceTest {

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

    @Test
    void shouldReturnEntities() throws IOException {
        // 读取测试JSON数据
        String exampleResponse = new String(Files.readAllBytes(Paths.get("entityPath/entitytest.json")));

        // 构造模拟的ClientResponse
        ClientResponse mockResponse = ClientResponse.create(HttpStatus.OK)
                .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
                .body(exampleResponse)
                .build();

        // 构建测试用的EntityService,传入mock的WebClient.Builder
        EntityService entityService = new EntityService(webClientBuilder);

        // Mock整个WebClient调用链
        when(webClientBuilder.baseUrl(anyString())).thenReturn(webClientBuilder);
        when(webClientBuilder.build()).thenReturn(webClient);
        when(webClient.get()).thenReturn(requestHeadersUriSpec);
        when(requestHeadersUriSpec.exchange()).thenReturn(Mono.just(mockResponse));
        
        // 根据JSON内容构造对应的EntityDTO实例
        EntityDTO mockEntityDTO = new EntityDTO();
        mockEntityDTO.setEntityDetails(List.of(new EntityDetail()));
        when(mockResponse.bodyToMono(EntityDTO.class)).thenReturn(Mono.just(mockEntityDTO));

        // 执行测试并验证结果
        Mono<List<EntityDTO>> result = entityService.getNumber("test-uri");
        StepVerifier.create(result)
                .expectNextMatches(list -> !list.isEmpty()) // 根据预期结果调整验证逻辑
                .verifyComplete();
    }
}

关键注意点

  • 必须确保WebClient的每一步链式调用都被正确Mock,因为WebClient的API是链式设计,任何一步mock不到都会触发真实调用。
  • 测试中使用StepVerifier验证Reactor流的结果,这是Reactor测试的标准方式。
  • 如果服务涉及DiscoveryClient等其他依赖,也要确保这些依赖的mock逻辑正确,避免干扰WebClient的测试。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.15 17:50:42