Mockito中使用argThat匹配Iterator参数时验证失败问题
尝试验证Mock服务的myMethod方法是否被调用,且最后一个参数为Iterator类型。测试断言代码如下:
verify(myService).myMethod( ..., argThat(dataIterator -> iteratorEquals(dataIterator, dataToSave.iterator())));
编写的TestHelper工具方法:
public class TestHelpers { public static <T> boolean iteratorEquals(Iterator<T> x, Iterator<T> y) { while (x.hasNext() && y.hasNext()) { if (x.next() != y.next()) { return false; } } return x.hasNext() == y.hasNext(); } }
调试时确认该静态方法返回true,传入服务的参数符合预期,但验证失败。将断言改为以下形式后测试可通过:
verify(myService).myMethod( ..., any());
测试失败时的错误信息:
myService.myMethod(
...,); Wanted 1 time:
-> at ...(StorageClientTest.java:91)
But was 0 times.org.mockito.exceptions.verification.TooFewActualInvocations:
myService.myMethod(
...,); Wanted 1 time:
-> at ...(StorageClientTest.java:91)
But was 0 times.
核心原因:Iterator的一次性遍历特性
Iterator是单向、一次性的遍历工具,一旦调用next()或hasNext()遍历过元素,后续再调用时就会从当前位置继续,直到遍历完所有元素后,hasNext()会一直返回false。
你的测试中大概率存在以下情况:
- 调试过程中已经遍历过传入
myMethod的Iterator,导致Mockito执行匹配时,该Iterator已经没有剩余元素; - 或
dataToSave.iterator()生成的Iterator,在之前的代码中已经被遍历过,导致匹配时两边的Iterator状态不一致。
即使调试时看到iteratorEquals返回true,也可能是调试时的遍历操作改变了Iterator的状态,实际Mockito执行验证时,Iterator已经处于耗尽状态,最终匹配失败。
额外潜在问题:元素比较方式错误
你的iteratorEquals方法中用x.next() != y.next()做元素比较,这是引用比较。如果元素是自定义对象而非基本类型/字符串,即使内容相同,引用不同也会返回false。虽然你调试时方法返回true,但如果后续测试场景中存在对象引用不同的情况,也会导致匹配失败,建议改为值比较。
解决方案
改用集合进行匹配
将Iterator转换为集合后再比较,彻底避免Iterator一次性遍历的问题:verify(myService).myMethod( ..., argThat(dataIterator -> { List<Object> actualList = new ArrayList<>(); dataIterator.forEachRemaining(actualList::add); List<Object> expectedList = new ArrayList<>(dataToSave); return expectedList.equals(actualList); }) );这种方式把Iterator的元素转存到集合中,集合可以反复比较,不会受遍历状态影响。
确保匹配时使用未被遍历的Iterator
如果必须使用Iterator匹配,要保证:- 传入
myMethod的Iterator在验证前没有被任何代码(包括调试器)遍历过; dataToSave.iterator()每次生成的都是新的、未被遍历的Iterator(比如dataToSave是ArrayList这类可重复生成Iterator的集合)。
- 传入
修复元素比较逻辑
修改iteratorEquals方法的元素比较方式,改为值比较:public static <T> boolean iteratorEquals(Iterator<T> x, Iterator<T> y) { while (x.hasNext() && y.hasNext()) { T xElem = x.next(); T yElem = y.next(); if (!Objects.equals(xElem, yElem)) { return false; } } return x.hasNext() == y.hasNext(); }
内容的提问来源于stack exchange,提问作者Tom Carmi

