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

Mockito单元测试mock SOAP客户端报存根参数不匹配错误

问题现象

对SOAP客户端编写单元测试做Mock时,运行测试抛出org.mockito.exceptions.misusing.PotentialStubbingProblem异常,错误提示为Strict stubbing argument mismatch(严格存根参数不匹配),报错指向webServiceTemplate.marshalSendAndReceive()方法调用。

关联代码

被测业务代码

public Respproxy updateId(final ReqProxy req) {
    Req request = createReq(req);
    Rpy response = (Rpy) webServiceTemplate.marshalSendAndReceive("www.example.com/path", request);
    return Respproxy.toBuilder().id(response.getId()).build();
}

private Req createReq() {
    Req updateReq = new Req();
    updateReq.setDate(LocalTime.now());
    return updateReq;
}

初始编写的单元测试代码

@Test
void testUpdateId() {
    // Req mockReq = mock(Req.class);
    Rpy mockRpy = mock(Rpy.class);

    when(webServiceTemplate.marshalSendAndReceive("null/UpdateId", mockReq())).thenReturn(mockRpy);
    updateIdSoapClient.updateId(Reqproxy.toBuilder.id(123));
    verify(webServiceTemplate, times(1)).marshalSendAndReceive(mockReq());
}

private Req mockReq() {
    Req updateReq = new Req();
    updateReq.setDate(LocalTime.now());
    return updateReq;
}
异常产生原因
  • 存根传入的目标地址和业务代码实际调用地址不匹配:测试代码存根时写的地址是"null/UpdateId",但业务代码实际调用marshalSendAndReceive传入的第一个参数是"www.example.com/path",第一个参数就无法匹配。
  • 动态生成的请求对象无法通过默认参数校验:业务代码的createReq()每次调用都会生成新的Req实例,且date字段取调用当下的系统时间;测试代码中mockReq()每次调用也会生成新的Req实例、取当下系统时间,两个实例不仅内存地址不同,date字段也会因为调用时机差存在毫秒级偏差。Mockito严格存根模式下默认用equals()方法做参数匹配,两个属性不完全一致的不同实例自然无法匹配。
  • 校验阶段参数同样不匹配:verify中传入的mockReq()是第三次生成的新Req实例,和业务代码实际调用时传入的实例也不是同一个对象,同样会触发参数校验失败。
修复方案
  1. 修正存根的请求地址,和业务代码实际传入的地址保持一致,即使用"www.example.com/path"作为第一个参数。
  2. 针对动态生成的Req参数,不要手动new实例做精确匹配,使用Mockito提供的参数匹配器:
    • 如果不需要校验Req内部的字段细节,直接用any(Req.class)匹配任意Req类型的实例即可
    • 如果需要校验Req的字段合法性,用argThat()自定义匹配规则,忽略动态变化的date字段,只校验需要断言的固定字段
  3. verify校验阶段同样使用和存根一致的参数匹配器,不要手动生成新的Req实例传入。
  4. 补全返回值存根和结果断言,保证测试逻辑完整。

修复后的参考测试代码

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

@Test
void testUpdateId() {
    Rpy mockRpy = mock(Rpy.class);
    // 修正地址,用any匹配动态生成的Req实例
    when(webServiceTemplate.marshalSendAndReceive(eq("www.example.com/path"), any(Req.class)))
            .thenReturn(mockRpy);
    // 补全返回值存根
    when(mockRpy.getId()).thenReturn(123);

    // 修正原测试代码中builder的调用笔误,补全build()
    Respproxy result = updateIdSoapClient.updateId(ReqProxy.builder().id(123).build());
    // 断言返回结果符合预期
    assertEquals(123, result.getId());
    // verify阶段使用相同的参数匹配器
    verify(webServiceTemplate, times(1))
            .marshalSendAndReceive(eq("www.example.com/path"), any(Req.class));
}

不推荐通过给测试类加@MockitoSettings(strictness = Strictness.LENIENT)关闭严格存根模式的方式绕过报错,严格模式可以提前发现无意义存根、参数不匹配等测试逻辑漏洞,关闭后会降低测试用例的可靠性。


内容的提问来源于stack exchange,提问作者Guru Cse

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 11:24:39