我想验证字符的特定顺序,以确保它们不会被乱码。我试着用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)
。