使用Mockito模拟WebClient时出现Stubbing错误的技术问询
解决WebClient单元测试中的Stubbing参数不匹配问题
问题背景
需要在单元测试中模拟WebClient(不使用WireMock或MockWebServer),被测方法逻辑如下:
public class CoreCustAdapter { private final String coreCustBaseUrl; private final WebClient webClient; // 构造方法省略 public PrivateCustomer getPrivateCustomerBasicInformation(String bankId, String customerId) { return webClient.get() .uri(coreCustBaseUrl, bankId) .headers(httpHeaders -> httpHeaders.set(APICommons.CUSTOMER_ID_VARIABLE_NAME, customerId)) .retrieve() .bodyToMono(PrivateCustomer.class) .block(); } }
对应的单元测试运行时抛出PotentialStubbingProblem异常,核心错误为Strict stubbing argument mismatch:
- 被测方法调用
headers时传入的是CoreCustCustomerBasicInfoAdapter中的Lambda实例- 测试代码中Stubbing用的是
CoreCustCustomerBasicInfoAdapterTest#getHttpHeaders()返回的Consumer实例
两个实例不是同一个对象,Mockito默认按对象相等性匹配,导致Stub不生效。
错误原因
Mockito的严格存根模式要求Stub的参数和实际调用的参数完全匹配。测试中用getHttpHeaders()生成的Consumer实例,和被测方法内定义的Lambda是两个不同的对象,因此参数匹配失败,触发异常。
解决方案
方法1:使用参数匹配器忽略实例差异
修改测试中headers方法的Stubbing,用any(Consumer.class)匹配任意Consumer参数,绕过实例相等性检查:
// 替换原来的when(requestHeadersSpec.headers(getHttpHeaders())).thenReturn(requestHeadersSpec); when(requestHeadersSpec.headers(any(Consumer.class))).thenReturn(requestHeadersSpec);
这种方式简单直接,适合只关注WebClient调用流程、不需要验证header具体值的场景。
方法2:捕获并验证Consumer行为(更严谨)
如果需要确保header被正确设置,可以用ArgumentCaptor捕获传入的Consumer,然后验证它的执行逻辑:
@Test void getPrivateCustomerBasicInformation_happyFlow() { Mono<PrivateCustomer> privateCustomerMono = MockData.getPrivateCustomerBasicInfo( "src/test/resources/core-cust-private-customer-basicinfo.json" ); // 1. 定义ArgumentCaptor用于捕获Consumer<HttpHeaders> ArgumentCaptor<Consumer<HttpHeaders>> headersCaptor = ArgumentCaptor.forClass(Consumer.class); // 2. 配置Stubbing,使用captor匹配参数 when(webClient.get()).thenReturn(requestHeadersUriSpec); when(requestHeadersUriSpec.uri( MockData.CORE_CUST_BASE_URL + APIEndpoints.CORE_CUST_PRIVATE_CUSTOMER_BASIC_INFO, MockData.BANK_ID )).thenReturn(requestHeadersSpec); // 用captor匹配headers方法的参数 when(requestHeadersSpec.headers(headersCaptor.capture())).thenReturn(requestHeadersSpec); when(requestHeadersSpec.retrieve()).thenReturn(responseSpec); when(responseSpec.bodyToMono(PrivateCustomer.class)).thenReturn(privateCustomerMono); when(spacetimeHttpReactive.createWebClient(externalSystem)).thenReturn(webClient); // 3. 执行被测方法 CoreCustCustomerBasicInfoAdapter adapter = new CoreCustCustomerBasicInfoAdapter( MockData.CORE_CUST_BASE_URL, spacetimeHttpReactive ); PrivateCustomer privateCustomer = adapter.getPrivateCustomerBasicInformation( MockData.BANK_ID, MockData.CUSTOMER_ID ); // 4. 验证捕获的Consumer是否正确设置了header HttpHeaders capturedHeaders = new HttpHeaders(); headersCaptor.getValue().accept(capturedHeaders); Assertions.assertEquals( MockData.CUSTOMER_ID, capturedHeaders.getFirst(APICommons.CUSTOMER_ID_VARIABLE_NAME) ); // 验证返回结果 Assertions.assertNotNull(privateCustomer); Assertions.assertEquals(MockData.CUSTOMER_UID, privateCustomer.customerUid()); }
额外优化建议
- 测试类中已经通过
@Mock注解注入了requestHeadersUriSpec、requestHeadersSpec、responseSpec,无需在测试方法中再次调用mock()创建实例,避免对象混淆。 - 确保
spacetimeHttpReactive和externalSystem也是通过@Mock注入的测试依赖,防止空指针异常。
内容的提问来源于stack exchange,提问作者Kaliyug Antagonist
相关产品推荐
相关产品推荐

