测试含可变集合的方法:寻求替代ArgumentCaptor的Hamcrest匹配器
这确实是个测试中常见的头疼问题——当你要校验的集合在方法调用后立刻被清空时,ArgumentCaptor就彻底失效了。不过别担心,我们有两种优雅的方案可以实现在方法调用瞬间即时校验集合状态的需求,还能完美复用Hamcrest的hasSize()逻辑:
方案1:用Mockito Answer实现即时校验(最省心)
不需要写额外的类,直接在Mock对象的方法调用时插入校验逻辑,完全避开ArgumentCaptor的坑:
// 假设sender是你Mock出来的依赖对象 doAnswer(invocation -> { // 在send方法被调用的瞬间,直接拿到传入的buffer(此时还没执行clear) List<LogMessage> sentBuffer = invocation.getArgument(0); // 这里直接用Hamcrest的hasSize做校验 assertThat(sentBuffer, hasSize(expectedBufferSize)); return null; // 因为send是void方法,返回null即可 }).when(sender).send(anyList()); // 执行要测试的核心方法 yourTestObject.flushBuffer();
这个思路的核心是在send(buffer)执行的瞬间就完成校验,完全不等后续的buffer.clear()执行,自然不会有集合被清空的问题。
方案2:自定义Hamcrest匹配器(符合你想要的collectionWhich风格)
如果你更倾向于用Hamcrest的匹配器风格,方便复用,可以写一个自定义匹配器,在参数匹配的瞬间完成校验:
import org.hamcrest.BaseMatcher; import org.hamcrest.Description; import org.hamcrest.Matcher; import java.util.Iterator; public class InstantCollectionMatchers { public static <T> Matcher<Iterable<T>> hasSizeInstantly(int expectedSize) { return new BaseMatcher<>() { @Override public boolean matches(Object item) { if (!(item instanceof Iterable)) { return false; } // 手动计算集合大小(避免依赖spliterator的精确值) int actualSize = 0; Iterator<T> iterator = ((Iterable<T>) item).iterator(); while (iterator.hasNext()) { actualSize++; iterator.next(); } return actualSize == expectedSize; } @Override public void describeTo(Description description) { description.appendText("a collection with size ").appendValue(expectedSize); } }; } }
使用的时候结合Mockito的argThat,同样是在方法调用时即时校验:
// 验证send方法被调用时,传入的集合符合我们的即时大小匹配器 verify(sender).send(argThat(InstantCollectionMatchers.hasSizeInstantly(expectedBufferSize))); // 执行测试方法 yourTestObject.flushBuffer();
这个匹配器可以在任何需要即时校验集合状态的场景复用,完全符合你想要的“collectionWhich”式的方案。
备选小技巧:临时快照(适合简单场景)
如果只是临时测试不想写太多代码,也可以在Answer里先复制集合快照再校验:
ArgumentCaptor<List<LogMessage>> captor = ArgumentCaptor.forClass(List.class); doAnswer(invocation -> { // 先复制一份快照,避免后续clear影响 List<LogMessage> bufferSnapshot = new ArrayList<>(invocation.getArgument(0)); assertThat(bufferSnapshot, hasSize(expectedBufferSize)); return invocation.callRealMethod(); // 如果需要执行send的真实逻辑 }).when(sender).send(captor.capture()); yourTestObject.flushBuffer();
不过这个方案不如前两个优雅,更适合临时应急。
内容的提问来源于stack exchange,提问作者Denys Kurochkin
相关产品推荐
相关产品推荐

