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

