为什么 Mockito 调用我的匹配器两次?

发布于 2024-10-20 02:42:03 字数 2379 浏览 1 评论 0原文

我有一个 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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

冷心人i 2024-10-27 02:42:04

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

ArgumentCaptor<ByteBuffer> captor = ArgumentCaptor.forClass(ByteBuffer.class);
verify(channel).write(captor.capture());
assertThat(captor.getValue().order(), equalTo(ByteOrder.LITTLE_ENDIAN));

It would be better to use ArgumentCaptor to verify argument to avoid custom matcher invoked twice.

ArgumentCaptor<ByteBuffer> captor = ArgumentCaptor.forClass(ByteBuffer.class);
verify(channel).write(captor.capture());
assertThat(captor.getValue().order(), equalTo(ByteOrder.LITTLE_ENDIAN));
楠木可依 2024-10-27 02:42:03

我不认为你的匹配器被调用了两次,你只需要在读取缓冲区之前倒带

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 开发人员。我建议每次在 matchesSafely 方法中倒带缓冲区来解决问题不会有什么坏处 - 它不应该损害正确性。

I don't think that your matcher gets called twice, you just have to rewind your buffer before reading from it:

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

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 is Times.verify method which is actually verifies 2 things:

  1. if there are any missing invocations of the method
  2. that the number of invocations of the method is exactly what is required

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 the matchesSafely method every time to fix the problem - it should not hurt the correctness.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文