为什么 Mockito 调用我的匹配器两次?
我有一个 Mockito 测试,看起来有点像这样(当然是简化的):
@RunWith(MockitoJUnitRunner.class)
public class BlahTest {
private static final int VERSION = 41;
private static final int PAGE_SIZE = 4096;
@Mock private FileChannel channel;
@Test
public void shouldWriteStandardHeader() throws Exception {
final Blah blah = new Blah(channel, VERSION, PAGE_SIZE);
blah.create();
verify(channel).write(littleEndianByteBufferContaining(Blah.MAGIC_NUMBER,
VERSION,
PAGE_SIZE));
}
private ByteBuffer littleEndianByteBufferContaining(final int... ints) {
return argThat(byteBufferMatcher(ints));
}
private Matcher<ByteBuffer> byteBufferMatcher(final int... ints) {
return new TypeSafeMatcher<ByteBuffer>() {
@Override
public void describeTo(final Description description) {
description.appendText("a little-endian byte buffer containing integers ").
appendValueList("", ",", "", ints);
}
@Override
protected boolean matchesSafely(final ByteBuffer buffer) {
if (buffer.order() != ByteOrder.LITTLE_ENDIAN) {
return false;
}
for (final int i : ints) {
if (buffer.getInt() != i) {
return false;
}
}
return true;
}
};
}
}
本质上,这个测试试图断言当调用 Blah.create()
时,它会写入一个 ByteBuffer
包含 FileChannel
的某些数据。
当我运行此测试时,匹配器被调用两次。这会导致 BufferUnderflowException
。
现在,我可以通过让匹配器在 matchesSafely
调用开始时存储缓冲区位置并将该位置移回到末尾(在finally块中)来解决这个问题,但看起来对我来说,我的匹配器不应该被调用两次。
任何人都可以阐明这一点吗?
编辑 #1:
可能值得注意的是,缓冲区在传递到通道之前会被翻转,因此位置为 0,并且限制设置为写入的数据量。
我已经调试了测试,并且匹配器肯定被调用了两次。
我可以通过在 matchesSafely()
开头标记缓冲区并在最后重置它来使测试通过,以便第二次通过匹配器读取相同的数据。这也证实了匹配器被调用了两次,否则它仍然会失败。
编辑#2:
看起来这是 Mockito 框架的预期行为。回想起来,我的匹配器有点差,因为它修改了全局状态。我修改了匹配器以记录起始位置,并在 matchesSafely()
方法结束时返回到它。无论如何,这可能是一个好主意,因为它可以节省修改全局状态的时间。出于同样的原因,我不使用 mark()
和 reset()
。
I've got a Mockito test that looks a bit like this (simplified, of course):
@RunWith(MockitoJUnitRunner.class)
public class BlahTest {
private static final int VERSION = 41;
private static final int PAGE_SIZE = 4096;
@Mock private FileChannel channel;
@Test
public void shouldWriteStandardHeader() throws Exception {
final Blah blah = new Blah(channel, VERSION, PAGE_SIZE);
blah.create();
verify(channel).write(littleEndianByteBufferContaining(Blah.MAGIC_NUMBER,
VERSION,
PAGE_SIZE));
}
private ByteBuffer littleEndianByteBufferContaining(final int... ints) {
return argThat(byteBufferMatcher(ints));
}
private Matcher<ByteBuffer> byteBufferMatcher(final int... ints) {
return new TypeSafeMatcher<ByteBuffer>() {
@Override
public void describeTo(final Description description) {
description.appendText("a little-endian byte buffer containing integers ").
appendValueList("", ",", "", ints);
}
@Override
protected boolean matchesSafely(final ByteBuffer buffer) {
if (buffer.order() != ByteOrder.LITTLE_ENDIAN) {
return false;
}
for (final int i : ints) {
if (buffer.getInt() != i) {
return false;
}
}
return true;
}
};
}
}
Essentially, this test is trying to assert that when Blah.create()
is invoked, it writes a ByteBuffer
containing certain data to the FileChannel
.
When I run this test, the matcher gets called twice. This results in a BufferUnderflowException
.
Now, I could get around this by just having the matcher store the buffer position at the beginning of the matchesSafely
call and move the position back to that at the end (in a finally block), but it seems to me that my matcher shouldn't be being called twice.
Can anyone shed any light on this?
EDIT #1:
It's probably worth noting that the buffer is flipped before being passed to the channel, so the position is 0 and the limit is set to the amount of data written.
I've debugged the test, and the matcher is definitely getting called twice.
I can make the test pass by marking the buffer at the beginning of matchesSafely()
and resetting it at the end, so the second pass through the matcher reads the same data. This also confirms that the matcher is getting called twice, as otherwise it would still fail.
EDIT #2:
So it looks like this is expected behaviour of the Mockito framework. In retrospect, my matcher is a bit poor because it modifies global state. I have modified the matcher to record the starting position and seek back to it at the end of the matchesSafely()
method. This is probably a good idea anyway since it saves modifying global state. I don't use mark()
and reset()
for the same reason.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
最好使用 ArgumentCaptor 来验证参数以避免调用两次自定义匹配器。
It would be better to use
ArgumentCaptor
to verify argument to avoid custom matcher invoked twice.我不认为你的匹配器被调用了两次,你只需要在读取缓冲区之前
倒带
:更新
所以,看起来它实际上确实被调用了两次。最终这一切都发生在
verify
方法中。如果你看一下 Mockito 源代码,就会发现 Times.verify 方法实际上验证了两件事:Mockito 包含
channel
模拟对象上所有方法的实际调用列表。为了验证这些调用中哪些是正确的,它将每个调用与您的匹配器进行匹配。它实际上做了两次。请查看来源以了解整个想法。我不确定这是否是一个错误,你应该询问 Mockito 开发人员。我建议每次在
matchesSafely
方法中倒带
缓冲区来解决问题不会有什么坏处 - 它不应该损害正确性。I don't think that your matcher gets called twice, you just have to
rewind
your buffer before reading from it:UPDATE
So, appears it actually does get called twice. It eventually it all happens in
verify
method. If you take a look at Mockito sources there isTimes.verify
method which is actually verifies 2 things:Mockito holds a list of actual invocations of all methods on your
channel
mock object. To verify which of these invocations are correct it matches every invocation with your matcher. And it actually does it twice. Please take a look at the sources to get the whole idea.I'm not sure if it's a bug or not, you should ask Mockito devs. I suggest it won't hurt to
rewind
your buffer in thematchesSafely
method every time to fix the problem - it should not hurt the correctness.