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

如何在JUnit测试中Mock WebClient以测试调用外部接口的Service方法?

解决WebClient Mock时的空指针异常

核心问题及修复步骤

  1. 错误调用静态方法
    测试代码里调用Service.callExternal(request)是静态调用,但目标方法是实例方法,必须用@InjectMocks注入的service实例调用,否则类内的webClient实例变量未初始化,直接抛出空指针。
    修正为:Flux<String> serviceResponse = service.callExternal(request);

  2. 移除重复的Mock初始化
    setUp方法的MockitoAnnotations.initMocks(this)和测试方法内的MockitoAnnotations.openMocks(this)重复,二者选其一即可。推荐用Mockito 3.x+的openMocks,或者更简洁的@ExtendWith(MockitoExtension.class)注解替代@Before方法。

  3. 补全WebClient链式调用的Mock环节
    Service类的callExternal方法中,WebClient的调用链是:post() → uri() → accept() → body() → headers() → retrieve() → bodyToFlux(),测试代码缺少accept、body、headers这几个方法的Mock返回值设置,导致调用这些方法时返回null,触发后续空指针。需要为每个环节指定返回的Mock对象。

修正后的完整测试代码

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import org.springframework.http.MediaType;
import java.util.function.Consumer;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

@ExtendWith(MockitoExtension.class) // 替代手动初始化Mock,更简洁
public class ServiceTest {

    @Mock
    private WebClient webClient;

    @InjectMocks
    private Service service; // 实例变量,用于调用实例方法

    @Mock
    private Util util; // 如果util是Service的依赖,需要Mock避免空指针

    @Test
    public void testCallExternal() throws Exception {
        // 1. 准备测试数据
        Request request = Request.builder().id("1").build();
        Flux<String> mockResult = Flux.just("test");
        String externalURL = "external url"; // 和Service中的externalAPIUrl一致,或用anyString()匹配
        ExpectedRequest expectedRequest = new ExpectedRequest(); // 模拟转换后的请求对象

        // 2. Mock util的方法
        when(util.convertRequest(request)).thenReturn(expectedRequest);
        when(util.getToken()).thenReturn("mock-token");

        // 3. 初始化WebClient各环节的Mock对象
        WebClient.RequestBodyUriSpec requestBodyUriSpec = mock(WebClient.RequestBodyUriSpec.class);
        WebClient.RequestBodySpec requestBodySpec = mock(WebClient.RequestBodySpec.class);
        WebClient.RequestHeadersSpec<?> requestHeadersSpec = mock(WebClient.RequestHeadersSpec.class);
        WebClient.ResponseSpec responseSpec = mock(WebClient.ResponseSpec.class);

        // 4. 补全完整的链式调用Mock
        when(webClient.post()).thenReturn(requestBodyUriSpec);
        when(requestBodyUriSpec.uri(eq(externalURL))).thenReturn(requestBodySpec);
        when(requestBodySpec.accept(MediaType.APPLICATION_JSON)).thenReturn(requestBodySpec);
        when(requestBodySpec.body(eq(Mono.just(expectedRequest)), eq(ExpectedRequest.class))).thenReturn(requestHeadersSpec);
        when(requestHeadersSpec.headers(any(Consumer.class))).thenReturn(requestHeadersSpec);
        when(requestHeadersSpec.retrieve()).thenReturn(responseSpec);
        when(responseSpec.bodyToFlux(String.class)).thenReturn(mockResult);

        // 5. 调用实例方法
        Flux<String> serviceResponse = service.callExternal(request);

        // 6. 验证响应式流结果
        StepVerifier.create(serviceResponse)
                .expectNext("test")
                .verifyComplete();
    }
}

额外优化点

  • 用StepVerifier替代简单的assertThat,更适合测试Reactor响应式流,能验证流的完整生命周期。
  • 如果externalAPIUrl是Service的私有变量,可通过反射设置,或在Mock时用anyString()匹配任意URI,避免硬编码。
  • 若util是Service的依赖,必须Mock它的方法,否则util.convertRequest或util.getToken会抛出空指针。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.06 01:10:56