Mockito 捕获在捕获时未维护捕获的列表



在Mockito中,我们遇到捕获列表不返回预期结果的情况。测试用例:

  1. 我们将"点"添加到列表中
  2. 我们捕获列表
  3. 我们将"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不会改变您的列表。

相关内容

  • 没有找到相关文章