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实例,和业务代码实际调用时传入的实例也不是同一个对象,同样会触发参数校验失败。
修复方案
- 修正存根的请求地址,和业务代码实际传入的地址保持一致,即使用
"www.example.com/path"作为第一个参数。 - 针对动态生成的
Req参数,不要手动new实例做精确匹配,使用Mockito提供的参数匹配器:- 如果不需要校验
Req内部的字段细节,直接用any(Req.class)匹配任意Req类型的实例即可 - 如果需要校验
Req的字段合法性,用argThat()自定义匹配规则,忽略动态变化的date字段,只校验需要断言的固定字段
- 如果不需要校验
verify校验阶段同样使用和存根一致的参数匹配器,不要手动生成新的Req实例传入。- 补全返回值存根和结果断言,保证测试逻辑完整。
修复后的参考测试代码
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
相关产品推荐
相关产品推荐

