Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does Mockito call my matcher twice?

Tags:

mockito

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.

like image 234
dty Avatar asked Mar 03 '11 22:03

dty


2 Answers

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.

like image 81
denis.solonenko Avatar answered Oct 19 '22 17:10

denis.solonenko


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));
like image 31
Wenbing Li Avatar answered Oct 19 '22 18:10

Wenbing Li