Mockito-如何验证模拟对象从未被调用。

73
我正在寻找一种使用Mockito验证的方法,以确保在测试期间没有与给定模拟对象进行交互。对于给定方法使用verification mode never()可以轻松实现这一点,但是我还没有找到完整模拟对象的解决方案。
实际上我想要实现的是:在测试中验证控制台没有输出任何内容。在jUnit中的一般思路如下:
private PrintStream systemOut;

@Before
public void setUp() {
    // spy on System.out
    systemOut = spy(System.out);
}

@After
public void tearDown() {
    verify(systemOut, never());  // <-- that doesn't work, just shows the intention
}

一个 PrintStream 有很多方法,我真的不想用单独的验证来验证每个方法 - 对于 System.err 也是一样的... 所以我希望,如果有一个简单的解决方案,那么在我拥有良好的测试覆盖率的情况下,可以强制软件工程师(和我自己)在提交更改之前删除他们(我的)调试代码,如 System.out.println("Breakpoint#1");e.printStacktrace();
5个回答

96
使用以下代码:
import static org.mockito.Mockito.verifyZeroInteractions;

// ...

private PrintStream backup = System.out;

@Before
public void setUp() {
    System.setOut(mock(PrintStream.class));
}

@After
public void tearDown() {
    verifyZeroInteractions(System.out);
    System.setOut(backup);
}

看起来这是我能为这种方法做的最好的事情了。谢谢!(实际上,我已经将大部分逻辑隐藏在一个帮助类中,并且也必须将Logger静音;)) - Andreas Dolk
28
verifyZeroInteractions现已被弃用,推荐使用org.mockito.Mockito#verifyNoInteractions。该方法的作用相同,只是名称不同。 - Andy Birchall

14
verifyZeroInteractions(systemOut);

如评论所述,这在使用spy时无效。

要得到一个相当的但更完整的答案,请参考gontard对于此问题的回答。


(摊手)- 但是似乎无法与间谍一起使用,我必须模拟一个PrintStream并将其设置在System上... - Andreas Dolk
不看你的代码,我猜测这个失败的原因是当你对间谍进行存根操作时,存根中的方法调用实际上会被计算为一个需要验证的调用。以下是三个建议:(1)verifyNoMoreInvocations(ignoreStubs(mockOne, mockTwo)); - 这将像 verifyZeroInteractions 一样工作,但忽略了你已经存根的内容。(2)你是否使用 doReturn/doThrow/doAnswer 方法来设置你的存根?当涉及到间谍时,这些方法通常比 when...thenReturn/thenThrow/then 更好用。...继续 - Dawood ibn Kareem
建议(3)是发布更多的代码,这样这里的人可以更好地帮助你。 - Dawood ibn Kareem
@David,我的代码示例是完整的。没有更多相关的行了。但我会期待一个使用Mockito Spy的解决方案。如果我能够只监视 System.out 而不是暂时地用模拟替换流,那将会更加优雅。 - Andreas Dolk
好的,谢谢,我现在更清楚你需要什么了。我有一个解决方案与gontard和McDowell不同 - 我现在没有时间发布它,但我会在几个小时内发布。 - Dawood ibn Kareem

14

自从原始的正确答案之后,verifyZeroInteractions 已被弃用,请使用 verifyNoInteractions 代替:

import org.junit.jupiter.api.Test;

import static org.mockito.Mockito.*;

public class SOExample {

    @Test
    public void test() {
        Object mock = mock(Object.class);
        verifyNoInteractions(mock);
    }
}

5

你可以尝试一种略有不同的方法:

private PrintStream stdout;

@Before public void before() {
    stdout = System.out;
    OutputStream out = new OutputStream() {
        @Override public void write(int arg0) throws IOException {
            throw new RuntimeException("Not allowed");
        }
    };
    System.setOut(new PrintStream(out));
}

@After public void after() {
    System.setOut(stdout);
}

如果您愿意,可以将匿名类型替换为模拟对象并进行验证,如Don Roby所建议的

1
肯定是个好答案。不幸的是,这次我对使用Mockito的解决方案感兴趣,因此我会接受另一个答案。希望你不介意 :) - Andreas Dolk

2
解决这个问题的一种方法是重构正在测试的类,以允许注入可用于输出的PrintStream。这将使您能够进行单元测试,而不依赖于System类的行为。您可以使用包级私有构造函数进行注入,因为您只会从相应的测试类中使用它。所以看起来可能像这样。
public class MyClass{
    private PrintWriter systemOut;

    public MyClass(){
        this(System.out);
    }

    MyClass(PrintWriter systemOut){
        this.systemOut = systemOut;

        // ...any other initialisation processing that you need to do
    }
}

在类内部,无论何时调用后者,请改用 systemOut 变量。

现在,在测试类中,创建一个模拟的 PrintStream,并将其传递给包私有构造函数,以获取要测试的对象。现在,您可以从测试中运行任何操作,并使用 verify 来检查它们对模拟的 PrintStream 的影响。


啊,好的,但不完全是我想做的:我试图找到一种测试类的方法,而不用去修改它,特别是不需要添加仅用于测试的代码。但另一方面,如果我们愿意准备被测试的类,那么这确实是一个很好的方法! - Andreas Dolk

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接