2

Suppose I have a code to test

void myMethod()
{
  byte []data = new byte[1];
  data[0]='a';
  output.send(42, data);
  data[0]='b';
  output.send(55, data);
}

I write a test:

testSubject.myMethod();
verify(output).send(eq(42), aryEq(new byte[]{'a'}));
verify(output).send(eq(55), aryEq(new byte[]{'b'}));

The test will fail as the method implementation reuses the same array for both calls, it is impossible to match args of the first send invocation after method finishes, so techically the verify statements should be specified before the method call, something like an expectation.

What is the right way to test such methods?

kan
  • 28,279
  • 7
  • 71
  • 101
  • Check out the answers to this question: https://stackoverflow.com/questions/10066590/mockito-matcher-and-array-of-primitives – Gus Sep 21 '17 at 16:54
  • @Gus It does not answer my question. The problem in my case that both calls use the same array, so no way to match its first contents after. – kan Sep 21 '17 at 17:23
  • Yep, I see now. Interesting/subtle problem – Gus Sep 21 '17 at 18:01

2 Answers2

2

Well it looks like Mockito is a bit inconvenient here. It detects the method call and logs it (use mock(MyOutput.class, withSettings().verboseLogging()); to enable logging), but it stores the reference to the array you're passing and thus is influenced when you mutate the array. It then thinks the method call was send(42, [98]) rather than send(42, [97]).

A possible way to work with that is to use the expectations you've mentioned. For example you can use a counter and increment it if a call matched the expectations (it is really just a workaround and rather nasty):

MyOutput mock = mock(MyOutput.class, withSettings().verboseLogging());
Main subject = new Main(mock);
AtomicInteger correctCallsCounter = new AtomicInteger(0);
doAnswer(invocation -> correctCallsCounter.incrementAndGet()).when(mock).send(eq(42), aryEq(new byte[]{'a'}));
doAnswer(invocation -> correctCallsCounter.incrementAndGet()).when(mock).send(eq(55), aryEq(new byte[]{'b'}));

subject.myMethod();

assertThat(correctCallsCounter.get(), is(2));

It works, because doAnswer is triggered when the call happens and when the byte array hasn't been changed yet.

The big downside of this workaround is, that it only works with void methods. If send would return "something", then I currently don't see a way to work around that.
Well and the other one is that this is obviously a rather nasty workaround.

So I would suggest to refactor your code a bit (use a new array) if that is possible. That would avoid these issues here.


If your send method would indeed return something and your myMethod method would rely on it, then you would usually mock it this way (send is expected to return a String in this example):

when(mock.send(eq(55), aryEq(new byte[]{'b'}))).thenReturn("something");

In order to still use the above mentioned workaround, you could change the doAnswer method to increment your counter and return your String (which you would mock anyway, thus it isn't that bad):

doAnswer(invocation -> {
    correctCallsCounter.incrementAndGet();
    return "something";
}).when(mock).send(eq(42), aryEq(new byte[]{'a'}));
Tom
  • 16,842
  • 17
  • 45
  • 54
  • Credits are going to Makoto, who had the general idea of setting expectations, but used `doNothing` which sadly doesn't help use here :(. – Tom Sep 21 '17 at 17:49
  • Similar questions where asked on Google Groups, but they also didn't have a good solution, since Mockito can't do much about that: https://groups.google.com/forum/?_escaped_fragment_=topic/mockito/LCWCaqW2eZ8#!topic/mockito/LCWCaqW2eZ8 – Tom Sep 21 '17 at 17:53
0

Use an Answer to copy the value of the parameter. Here is some code (it is not pretty):

public class TestMyClass
{
    private static List<byte[]> mockDataList = new ArrayList<>();

    @InjectMocks
    private MyClass classToTest;

    private InOrder inOrder;

    @Mock
    private ObjectClass mockOutputClass;

    @After
    public void afterTest()
    {
        inOrder.verifyNoMoreInteractions();

        verifyNoMoreInteractions(mockOutputClass);
    }

    @Before
    public void beforeTest()
    {
        MockitoAnnotations.initMocks(this);

        doAnswer(new Answer()
        {
            @Override
            public Object answer(
                final InvocationOnMock invocation)
                    throws Throwable
            {
                final byte[] copy;
                final byte[] source = invocation.getArgument(1);

                copy = new byte[source.length];
                System.arraycopy(source, 0, copy, 0, source.length);

                mockDataList.add(copy);

                return null;
            }
        }).when(mockOutputClass).send(anyInt(), any(byte[].class));;


        inOrder = inOrder(
            mockOutputClass);
    }

    @Test
    public void myMethod_success()
    {
        byte[] actualParameter;
        final byte[] expectedFirstArray = { (byte)'a' };
        final byte[] expectedSecondArray = { (byte)'b' };


        classToTest.myMethod();


        actualParameter = mockDataList.get(0);
        assertArrayEquals(
            expectedFirstArray,
            actualParameter);

        inOrder.verify(mockOutputClass).send(eq(42), any(byte[].class));


        actualParameter = mockDataList.get(1);
        assertArrayEquals(
            expectedSecondArray,
            actualParameter);

        inOrder.verify(mockOutputClass).send(eq(55), any(byte[].class));
    }
}

Note that the value of the parameter is compared separate of the verification of the call, but the order of the parameters is still verified (i.e. 'a' array first, then 'b' array).

DwB
  • 37,124
  • 11
  • 56
  • 82