如何Mock服务类构造函数内创建的WebClient?
单元测试Mock WebClient的解决方案(不使用构造注入/ReflectionTestUtils)
针对你提供的Service类,这里有两种比ReflectionTestUtils更优雅的Mock方案:
方案1:提取WebClient创建逻辑为可扩展方法
将原类中创建WebClient的逻辑抽成包私有或protected方法,测试时通过重写该方法返回Mock对象,无需破坏封装。
修改原Service类
@Service public class Service { private final WebClient webClient; public Service(String baseUrl) { this.webClient = createWebClient(baseUrl); } // 包私有方法,供测试重写 WebClient createWebClient(String baseUrl) { return WebClient.builder().baseUrl(baseUrl).build(); } public User getUser() { // 补充retrieve()方法,原代码遗漏该调用环节 return webClient.get().retrieve().bodyToMono(User.class).block(); } }
测试实现(Mockito Spy方式)
@ExtendWith(MockitoExtension.class) public class ServiceTest { @Mock private WebClient mockWebClient; @Mock private WebClient.RequestHeadersUriSpec<?> requestHeadersUriSpec; @Mock private WebClient.ResponseSpec responseSpec; @Test void testGetUser() { // 创建Service的Spy实例,重写createWebClient方法返回Mock对象 Service service = Mockito.spy(new Service("http://dummy-url")); Mockito.doReturn(mockWebClient).when(service).createWebClient(Mockito.anyString()); // 模拟WebClient调用链 Mockito.when(mockWebClient.get()).thenReturn(requestHeadersUriSpec); Mockito.when(requestHeadersUriSpec.retrieve()).thenReturn(responseSpec); Mockito.when(responseSpec.bodyToMono(User.class)) .thenReturn(Mono.just(new User("testUser"))); User result = service.getUser(); assert result != null; assert result.getName().equals("testUser"); } }
也可以直接继承Service类编写测试子类,跳过Mockito依赖:
public class ServiceTest { private WebClient mockWebClient = Mockito.mock(WebClient.class); // 初始化其他Mock对象... @Test void testGetUser() { Service testService = new Service("http://dummy-url") { @Override WebClient createWebClient(String baseUrl) { return mockWebClient; } }; // 模拟调用链并执行断言,逻辑同Spy方式 } }
方案2:使用WireMock模拟真实HTTP服务
如果不想修改原Service类,可以用WireMock启动本地模拟HTTP服务,让WebClient调用该服务地址,模拟后端响应。这种方式更贴近真实场景,无需Mock WebClient本身。
测试实现
@ExtendWith(WireMockExtension.class) public class ServiceTest { @Test void testGetUser(@WireMockTestServer WireMockServer wireMockServer) { // 配置WireMock返回模拟的JSON响应 wireMockServer.stubFor(WireMock.get(WireMock.urlEqualTo("/")) .willReturn(WireMock.aResponse() .withHeader("Content-Type", "application/json") .withBody("{\"name\":\"testUser\"}"))); // 创建Service时传入WireMock的服务地址 Service service = new Service(wireMockServer.baseUrl()); User result = service.getUser(); assert result != null; assert result.getName().equals("testUser"); } }
方案优势对比
- 相比
ReflectionTestUtils,这两种方案都不依赖类的内部字段结构,避免了反射带来的封装破坏和维护风险; - 方案1利用面向对象的扩展特性,符合开闭原则;
- 方案2模拟真实HTTP交互,测试可信度更高,同时无需修改业务代码。
内容的提问来源于stack exchange,提问作者Yi Qian
相关产品推荐
相关产品推荐

