如何在JUnit测试中Mock WebClient以测试调用外部接口的Service方法?
解决WebClient Mock时的空指针异常
核心问题及修复步骤
错误调用静态方法
测试代码里调用Service.callExternal(request)是静态调用,但目标方法是实例方法,必须用@InjectMocks注入的service实例调用,否则类内的webClient实例变量未初始化,直接抛出空指针。
修正为:Flux<String> serviceResponse = service.callExternal(request);移除重复的Mock初始化
setUp方法的MockitoAnnotations.initMocks(this)和测试方法内的MockitoAnnotations.openMocks(this)重复,二者选其一即可。推荐用Mockito 3.x+的openMocks,或者更简洁的@ExtendWith(MockitoExtension.class)注解替代@Before方法。补全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
相关产品推荐
相关产品推荐

