无法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
相关产品推荐
相关产品推荐

