在没有 ArgumentCaptor 的情况下匹配可变对象

Matching mutable object without ArgumentCaptor

我必须测试一个使用可变对象的方法

private final List<LogMessage> buffer;
...
flushBuffer() {
  sender.send(buffer);
  buffer.clear();
}

我需要测试它发送的缓冲区大小是否准确。
ArgumentCaptor 不适用,因为捕获的集合在断言时已清除。

是否有一种匹配器可以重用 Hamcrest 的 hasSize() 并在方法调用时正确检查?

我更喜欢这种假设的 collectionWhich 匹配器:

bufferedSender.flushBuffer();
verify(sender).send(collectionWhich(hasSize(5)));

您会遇到与 ArgumenCaptor 相同的问题,因为 verify() 方法在执行后检查调用与对象的状态。不执行捕获以仅保留调用时的状态。
因此,对于可变对象,我认为更好的方法是不使用 Mockito,而是创建 Sender class 的存根,您可以在其中捕获集合的实际大小,因为 send() 是调用。

这是一个示例存根 class(您当然可以 enrich/adapt 的最小示例):

class SenderStub extends Sender {

    private int bufferSize;
    private boolean isSendInvoked;

    public int getBufferSize() {
        return bufferSize;
    }

   public boolean isSendInvoked(){
      return isSendInvoked;
   }

    @Override
    public void send(List<LogMessage> buffer ) {
        this.isSendInvoked = true;
        this.bufferSize = buffer.size();
    }    
}

现在您可以检查是否调用了 Sender 及其大小(或更多)。

所以搁置 Mockito 来创建这个模拟并验证其行为:

SenderStub sender = new SenderStub();
MyClassToTest myClass = new MyClassToTest(sender);
// action
myClass.flushBuffer();
// assertion
Assert.assertTrue(sender.isInvoked()); 
Assert.assertEquals(5, sender.getBufferSize());

David 想法的轻量级替代方案:使用 Answer 在调用时制作副本。未经测试的代码,但这应该非常接近:

final List<LogMessage> capturedList = new ArrayList<>();
// This uses a lambda, but you could also do it with an anonymous inner class:
// new Answer<Void>() {
//   @Override public Void answer(InvocationOnMock invocation) { /* ... */ }
// }
when(sender.send(any())).thenAnswer(invocation -> {
  List<LogMessage> argument = (List<LogMessage>) invocation.getArguments()[0];
  capturedList.addAll(argument);
});
bufferedSender.flushBuffer();
assertThat(capturedList).hasSize(5);

Jeff Bowman 的回答很好,但我认为我们可以通过在 Answer 对象本身中内联断言来改进它。它避免创建不必要的复制对象和额外的局部变量。

除了在我们需要复制自定义对象的状态的情况下(通过执行它的深复制),这种方式要简单得多。事实上,它不需要任何自定义代码或库来执行副本,因为断言是即时完成的。

在 Java 8 中,它会给出:

import  static org.mockito.Mockito.*;

when(sender.send(any())).thenAnswer(invocation -> {
  List<LogMessage> listAtMockTime = invocation.getArguments()[0];
  Assert.assertEquals(5, listAtMockTime.getSize());
});
bufferedSender.flushBuffer();

请注意,InvocationOnMock.getArgument(int index) returns 是一个无限通配符 (?)。因此,调用者不需要强制转换,因为返回的类型由目标定义:这里是声明的变量,我们为其分配结果。