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

使用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 07:44:56