在Mockito中,我们遇到捕获列表不返回预期结果的情况。测试用例:
- 我们将"点"添加到列表中
- 我们捕获列表
- 我们将"Sok"添加到列表中。
在我们的断言中,我们只期望"Pip"在那里,但"Sok"也在那里。我们认为这是不正确的,因为在捕获时"Sok"不在列表中。
java.lang.AssertionError:
预期 :[点]
实际 :[点子,索克]
- 有没有人对此有解决方案?
- 这是 Mockito 中的错误还是功能?
- 为什么Mockito保留对列表的引用而不复制列出捕获时间?
这是测试用例:
@RunWith(MockitoJUnitRunner.class)
public class CaptureTest {
@Captor
private ArgumentCaptor<List> listCapture;
@Mock
private ListPrinter listPrinter;
private TestClass testClass;
@Before
public void setUp() {
testClass = new TestClass(listPrinter);
}
@Test
public void testCapture() {
testClass.simulateFailSituation();
verify(listPrinter).printList(listCapture.capture());
// THIS FAILS: Expected:[Pip], Actual:[Pip, Sok]
assertEquals(Collections.singletonList("Pip"), listCapture.getValue());
}
public class TestClass {
private List list = new ArrayList();
private ListPrinter listPrinter;
public TestClass(ListPrinter listPrinter) {
this.listPrinter = listPrinter;
}
private void simulateFailSituation() {
list.add("Pip");
listPrinter.printList(list);
list.add("Sok");
}
}
public interface ListPrinter {
void printList(List list);
}
}
这听起来像是一个了不起的功能,但可以这样想:如果它正在制作副本,它应该在哪里停止?您可能会捕获一些对其他对象具有许多引用的对象,并且最终可能会对 JVM 实例中的几乎所有对象进行深层复制。
那将是一个严重的性能打击,所以我有点理解为什么。
因此,您可以选择两种方法:
- 在适用的情况下使用不可变对象。除了更容易测试之外,它还使代码更易于阅读和调试。
- 调用时立即测试值,而不是捕获引用。对于 void 方法,您可以将
doAnswer
方法与Answer<Void>
一起使用,在那里测试它或制作副本。顺便说一下,这是新的,请参阅如何使用 mockito 制作模拟到无效的方法。
我发现它比验证强大得多。在您的情况下,doAnswer
可能如下所示:
doAnswer(invocation -> {
assertEquals(Collections.singletonList("Pip"), invocation.getArguments()[0]);
return null;
}).when(listPrinter).printList(Matchers.anyList());
为了同意Vlasec的回答,默认情况下Mockito深度复制是没有意义的。它将无法分辨哪些对象是不可变的值对象(如字符串),哪些对象易于复制(ArrayList?),哪些绝对不应该复制(Thread?),等等。但是,在调用该方法时,可以使用 Answer 创建自己的副本。
@Test
public void testCapture() {
// from memory - may need warnings suppressed or different casts/generics
List<String> listSnapshot = new ArrayList<>();
doAnswer(invocation -> {
listSnapshot.addAll((List) invocation.getArguments()[0]);
return null;
}).when(listPrinter).printList(any());
testClass.simulateFailSituation();
listCapture.capture());
assertEquals(Collections.singletonList("Pip"), listSnapshot);
}
尽管 Vlasec 的更新显示了在答案中执行验证的方法,但我更喜欢这种手动捕获,因为它更接近 ArgumentCaptor,并且更适合"给定/何时/然后"或"期望/执行/验证"测试格式。此外,在测试中失败的断言将导致 Mockito 失败,而受测系统位于堆栈上,这可能会导致比在测试方法返回后进行验证时更不明显的故障。
处理此问题的一种方法是调用
doAnswer(invocationOnMock -> {return null;}).when(listPrinter).printList(any());
然后在钩子中用深拷贝进行自己的捕获,或就地验证。
发生这种情况是因为 Mockito 维护了对作为参数传递的对象的引用。
你可以试试这个。
private void simulateFailSituation() {
list.add("Pip");
listPrinter.printList(new ArrayList(list));
list.add("Sok");
}
这样,您还可以确保printList
不会改变您的列表。