在没有 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 是一个无限通配符 (?
)。因此,调用者不需要强制转换,因为返回的类型由目标定义:这里是声明的变量,我们为其分配结果。
我必须测试一个使用可变对象的方法
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 是一个无限通配符 (?
)。因此,调用者不需要强制转换,因为返回的类型由目标定义:这里是声明的变量,我们为其分配结果。