【问题标题】:How to test in Mockito for specific order of calls with same arguments?如何在 Mockito 中测试具有相同参数的特定调用顺序?
【发布时间】:2011-12-04 02:52:08
【问题描述】:

我想验证字符的具体顺序,以确保它们不会出现乱码。我尝试使用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 测试?


编辑:归档为错误http://code.google.com/p/mockito/issues/detail?id=296

【问题讨论】:

    标签: java testing mocking mockito


    【解决方案1】:

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

    我会使用 ArgumentCaptor 而不是答案。我会在测试类中写一个这样的方法,然后用“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());
    }
    

    希望这会有所帮助。

    【讨论】:

    • 光滑。我不知道 arg captor 的那个特性。
    • 有时很容易检查结果/输出/结果(如果可能的话)然后阅读此测试代码。我的意思是,如果我们知道它应该返回我们:对于给定的可能参数,“我们期望的一些刺痛”。
    【解决方案2】:

    按顺序验证与某事完成的次数是一个独立的概念,因此当您到达“l”并告诉 Mockito 验证它是否发生时,它通过了按顺序检查,但由于“l”而失败' 调用了 3 次,而你(含蓄地)告诉它只期待一次。这是我之前在 Mockito 中遇到的一个怪癖,但几乎任何时候发生这种情况,我最终都会认为我的测试写得不好,当我修复它时,问题就消失了。在您的情况下,我会说验证写入 Writer 的每个字符是 方式 矫枉过正。如果要验证消息是否正确发送,则应将输入消息与输出消息进行比较。在您的示例中,这可能需要使用 StringWriter 而不是模拟作家。然后你的测试结束就像

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

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

    【讨论】:

    • 我正在编写一个虚拟机/解释器,因此 I/O 一次发生一个字符,没有完整消息的概念,只是打印到标准输出的一系列单个字符。我试图确保在打印它们的过程中我不会破坏任何东西(是的,我正在实现我自己的内部存储器)。我要验证的消息是“Hello World”,所以我认为测试这样的消息是否可以逐个字符正确打印并不过分。 :)
    • 嗨,如果这个测试很关键,即你应该测试你正在做什么,我同意@Ryan,你应该更喜欢输入与输出的比较。存根您的作者时,使用自定义答案,将字符附加到通常的StringBuilder 中,然后将其与我们的"Hello World" 输入进行比较。或者,您可能可以编写自定义匹配器; inOrder.verify(mockWriter, times(11)).write(charsThatMatchInOrder("Hello World")); 之类的东西,但是如果字符串发生变化,这个测试可能会很容易中断!
    • @Brice 很好,被模拟/解释的代码 sn-p 是测试输入的一部分,所以是的,如果它发生变化,预计测试会失败。
    • 我很遗憾写了这个评论,我怎么能不去想。正如大卫在他的回答中所写的那样,你真的应该支持 ArgumentCaptor。
    【解决方案3】:

    Mockito 这样工作的原因是按顺序验证和常规验证之间的一致性。换句话说,如果我们不以这种方式实现它,API 会以不同的方式令人惊讶:) 在尝试设计一个像样的 api 时,您需要做出权衡。

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

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

    希望有帮助!

    【讨论】:

    • 我不同意。我可以展开循环并且会遇到同样的问题。我知道这是一个事实,就像我在尝试调试它时所做的那样。其次,如果它是调用 InOrder 但它不能完全确定调用的顺序是不是明显违反了最小惊讶原则?
    【解决方案4】:

    我目前正在使用自定义答案解决此问题。

    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 来独立和更好地使用,但对于一般情况,List 效果更好.

    【讨论】:

    • 我在想什么... ArgumentCaptor 要好得多,它基本上可以完成这个自定义答案正在做的事情。大卫的回答在很多方面都更正确。
    【解决方案5】:

    这是一个奇怪的测试,但它仍然应该得到 mocking API 的支持。我相信它可以被 Mockito 支持,因为其他模拟 API 确实支持它。

    使用Unitils 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);
        }};
    }
    

    【讨论】:

      【解决方案6】:

      无论何时使用inOrder,并且相同的调用需要与中间的其他调用进行验证(其他方法或带有其他参数的相同方法),您可以使用Mockito.calls(1)而不是times(1)

      【讨论】:

        猜你喜欢
        • 2011-12-26
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2022-12-05
        相关资源
        最近更新 更多