9

我有一个看起来有点像这样的 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()出于同样的原因。

4

2 回答 2

5

我认为您的匹配器不会被调用两次,您只需要rewind在读取缓冲区之前先读取它:

protected boolean matchesSafely(final ByteBuffer buffer) {
    if (buffer.order() != ByteOrder.LITTLE_ENDIAN) {
        return false;
    }
    buffer.rewind();
    ...
}

更新

所以,看起来它实际上确实被调用了两次。最终这一切都发生在verify方法中。如果您查看 Mockito 来源,则有一种Times.verify方法实际上可以验证两件事:

  1. 如果有任何缺少的方法调用
  2. 该方法的调用次数正是所需要的

Mockito 包含一个对channel模拟对象的所有方法的实际调用列表。为了验证这些调用中的哪一个是正确的,它将每次调用与您的匹配器匹配。它实际上做了两次。请查看来源以了解整个想法。

我不确定这是否是错误,您应该询问 Mockito 开发人员。我建议每次解决问题的方法都不会损害rewind您的缓冲区- 它不应该损害正确性。matchesSafely

于 2011-03-04T00:32:10.313 回答
4

最好使用ArgumentCaptor验证参数来避免自定义匹配器被调用两次。

ArgumentCaptor<ByteBuffer> captor = ArgumentCaptor.forClass(ByteBuffer.class);
verify(channel).write(captor.capture());
assertThat(captor.getValue().order(), equalTo(ByteOrder.LITTLE_ENDIAN));
于 2015-08-25T23:14:57.210 回答