如何在Mockito中测试具有相同参数的特定调用顺序



我想验证字符的特定顺序,以确保它们不会被乱码。我试着用InOrder写它,但它似乎不起作用,或者至少在Mockito 1.8.5中。

@Test
public void inOrderTest() throws IOException{
    final String message = "Hello World!n";
    for( char c : message.toCharArray() )
        mockWriter.write( c );
    final InOrder inOrder = inOrder( mockWriter );
    for( char c : message.toCharArray() )
        inOrder.verify( mockWriter ).write( c );
    inOrder.verifyNoMoreInteractions();
}

上面的测试失败,提示:

Verification in order failure:
mockWriter.write(108);
Wanted 1 time:
-> at     org.bitbucket.artbugorski.brainfuj.interpreter.InterpreterTest.inOrderTest(InterpreterTest.java:62)
But was 3 times. Undesired invocation:
-> at org.bitbucket.artbugorski.brainfuj.interpreter.InterpreterTest.inOrderTest(InterpreterTest.java:58)

如何编写一个Mockito测试?


EDIT:归档为bug http://code.google.com/p/mockito/issues/detail?id=296

向之前的回复者道歉;但在我看来,使用Answer有点违背了Mockito的一个基本思想,即存根和验证是两个完全独立的过程。Mockito有存根功能和验证功能,Mockito的开发者已经努力将两者分开。答案用于存根;虽然在一些情况下,答案是最好的验证方式,但我不认为这是其中之一。

我将使用ArgumentCaptor而不是Answer。我会在测试类中编写这样一个方法,然后以"Hello world"作为参数调用它。注意,我还没有测试过这个,所以它可能包含拼写错误。

private void verifyCharactersWritten( String expected ){
    ArgumentCaptor<Character> captor = ArgumentCaptor.forClass( Character.class ); 
    verify( mockWriter, times( expected.length())).write( captor.capture());
    assertEquals( Arrays.asList( expected.toCharArray()), captor.getAllValues());
}

顺序验证是一个独立的概念,所以当你到达'l'并告诉Mockito验证它是否发生时,它通过了顺序检查,但失败了,因为'l'调用了三次,而你(隐式地)告诉它只期望它一次。这是我之前在Mockito中遇到的一个怪癖,但几乎每次发生这种情况时,我都认为我的测试写得很糟糕,当我修复它时,问题就消失了。在您的情况下,我想说验证写入Writer的每个字符是一种过度的方式。如果要验证消息是否正确发送,则应该比较输入消息和输出消息。在您的示例中,这可能需要使用StringWriter而不是mock writer。然后测试的结尾看起来就像

assertThat(stringWriter.toString(), equalTo(message));

如果你真的必须做你正在做的事情,我所能建议的就是深入研究Mockito代码,看看是否有办法实现它,并可能提交一个bug报告,看看他们对此有什么看法。

Mockito这样工作的原因是顺序验证和常规验证之间的一致性。换句话说,如果我们不以这种方式实现它,那么API将以不同的方式令人惊讶:)当你试图设计一个体面的API时,你会做出权衡。

所以…问题的答案。首先,应该避免在测试代码中使用循环(或条件)之类的语句。原因是您非常关心测试代码的清晰度和可维护性!=)

如果我们从测试中删除循环,我们就不再有一个用例,尽管…如果没有用例,就很难给出答案。David的ArgumentCaptor可能是个不错的主意。

希望有帮助!

我目前正在破解这个自定义答案。

final List<Integer> writtenChars = new ArrayList<>();
willAnswer(
        new Answer(){
            @Override
            public Object answer( final InvocationOnMock invocation )throws Throwable {
                final int arg = (int) invocation.getArguments()[0];
                writtenChars.add( arg );
                return null;
            }
        }
    ).given( mockWriter ).write( anyInt() );

然后,在运行所需的方法之后,我根据列表测试期望的字符串。

final Iterator<Integer> writtenCharItr = writtenChars.iterator();
for( int charInt : "Hello World!n".toCharArray() )
    assertThat(  charInt, is( writtenCharItr.next() )  );
assertThat( "There are no more chars.", writtenCharItr.hasNext(), is(false) );
verify( mockWriter ).flush();

如果你对不止一次的方法调用感兴趣,除非你在列表中记录被调用的方法,否则这将不起作用。


编辑:向Brice道歉,您似乎已经独立地找到了这个解决方案,除了独立地和更好地使用StringBuilder而不是List,尽管对于一般情况下,列表工作得更好。

这是一个奇怪的测试,但是,mock API应该支持它。我相信它可以被Mockito支持,因为其他mock api都支持它。

With unitls Mock:

Mock<Writer> mockWriter;
@Test
public void inOrderTest() throws Exception {
    Writer writer = mockWriter.getMock();
    final String message = "Hello World!n";
    for (char c : message.toCharArray())
        writer.write(c);
    for (char c : message.toUpperCase().toCharArray())
        mockWriter.assertInvokedInSequence().write(c);
    MockUnitils.assertNoMoreInvocations();
}

或使用JMockit(我自己的工具):

@Test
public void inOrderTest(final Writer mockWriter) throws Exception {
    final String message = "Hello World!n";
    for (char c : message.toCharArray())
        mockWriter.write(c);
    new FullVerificationsInOrder() {{
        for (char c : message.toCharArray())
            mockWriter.write(c);
    }};
}

当使用inOrder时,相同的调用需要与之间的其他调用(其他方法或具有其他参数的相同方法)进行验证,您可以使用Mockito.calls(1)代替times(1)

相关内容

  • 没有找到相关文章

最新更新