如何设定Mockito的期望?

时间:2017-09-21 16:50:00

标签: java unit-testing mockito

假设我有一个代码来测试

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

我写了一个测试:

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

测试将失败,因为方法实现为两个调用重用相同的数组,在方法完成后不可能匹配第一个send调用的args,因此技术上应该在方法调用之前指定verify语句,就像期待一样。

测试此类方法的正确方法是什么?

2 个答案:

答案 0 :(得分:2)

看起来Mockito在这里有点不方便。它检测方法调用并将其记录(使用mock(MyOutput.class, withSettings().verboseLogging());启用日志记录),但它存储对您传递的数组的引用,因此在您更改数组时会受到影响。然后它认为方法调用是send(42, [98])而不是send(42, [97])

使用它的一种可能方法是使用您提到的期望。例如,你可以使用一个计数器,如果一个调用符合预期,它就会增加它(它真的只是一种解决方法而且非常讨厌):

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));

它有效,因为在调用发生时以及字节数组尚未更改时会触发doAnswer

此解决方法的一大缺点是,它仅适用于void方法。如果send会返回"某些内容",那么我目前无法找到解决问题的方法。
好吧,另一个是这显然是一个相当讨厌的解决方法。

所以我建议稍微重构你的代码(使用新的数组),如果可能的话。这样可以避免这些问题。

如果您的send方法确实会返回某些内容并且您的myMethod方法会依赖它,那么您通常会以这种方式进行模拟(send应该返回此字符串示例):

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

为了仍然使用上面提到的解决方法,你可以改变doAnswer方法来增加你的计数器并返回你的字符串(无论如何你都会嘲笑它,因此它不是那么糟糕):< / p>

doAnswer(invocation -> {
    correctCallsCounter.incrementAndGet();
    return "something";
}).when(mock).send(eq(42), aryEq(new byte[]{'a'}));

答案 1 :(得分:0)

使用Answer复制参数的值。 这是一些代码(它不漂亮):

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));
    }
}

请注意,参数的值与调用验证分开进行比较,但仍然验证参数的顺序(即'a'数组首先,然后是'b'数组)。