如何让Mockito模拟在进行意外调用时导致失败?

tcbh2hod  于 11个月前  发布在  其他
关注(0)|答案(4)|浏览(136)

我有一些模拟对象,它们可能会被传递一点,最终可能会相当复杂。
我想让Mockito为每个对mock的调用输出一个日志,或者我想让它在发生意外调用时失败,这样我就可以遍历这些调用并设置适当的响应。
我该如何实现这一点?

gupuwyp2

gupuwyp21#

最惯用的方法是使用verifyNoMoreInteractions,如Mockito docs #8:

//interactions
mock.doSomething();
mock.doSomethingUnexpected();

//verification
verify(mock).doSomething();
 
//following will fail because 'doSomethingUnexpected()' is unexpected
verifyNoMoreInteractions(mock);

字符串
我在上面说“most-idiomatic”是因为该方法有自己的警告标签,它链接到Mockito创始人Szczepan Faber的博客文章"Should I worry about the unexpected?"(在different URL上仍然可见)。
verifyNoMoreInteractions()并不推荐用于所有测试方法。verifyNoMoreInteractions()是来自交互测试工具包的一个方便的Assert。仅在相关时使用它。滥用它会导致过度指定,较少维护测试。
简而言之,你应该有一个非常明确的理由来检查你的依赖项 * 没有做什么 * 或者你的被测系统 * 没有调用什么 *,而不是他们正在做什么和调用什么。如果你想避免不必要的RPC调用,你可以使用verifyNoMoreInteractions作为RPC对象,但不是(比如说)一个没有副作用的计算器。更好的是用never()times(int)作为verify的参数来指定你的确切要求。
也就是说,有两种更不习惯的方法可以做到这一点:

  • 您可以获取使用mockingDetails(Object)并迭代getInvocations()的调用的总体日志。这应该会反射性地为您提供调用的完整列表。我很难想象这在测试中有什么用处,但它可能有助于清理模糊或缺乏文档的现有系统。
  • 您可以使mock的默认操作抛出异常,这意味着如果任何人调用您没有存根的东西,测试将立即失败。
// untested pseudocode
YourObject yourObject = Mockito.mock(YourObject.class, withSettings()
    .defaultAnswer(invocation -> {
      throw new UnsupportedOperationException(invocation.toString());
    }));


当然,这是可行的,但你不仅违反了Mockito的核心原则之一,(默认情况下,mock是 nice,使用EasyMock对“nice”的定义),但是您也可以强迫自己只使用doVerb存根(doReturndoAnswer等),因为对when(yourObject.doAnything())的调用必然会在对when的调用运行之前抛出该异常。
熟悉Mockito的开发人员可能会说,这种倾向于异常的治疗方法比疾病更糟糕,并且可能只对临时诊断最混乱的遗留代码有用。

kulphzqa

kulphzqa2#

我只是问自己同样的问题.使用ReturnsSmartNulls的解决方案将返回SmartNulls而不是null.所以它只对非void方法有意义,对吗?void方法呢?那些有副作用的方法?
在我看来,如果你想确保你的测试失败,当你的mock的一个方法被调用时,没有显式的行为定义(doXXX(...).when(...) mockito方法),你可以用一个自定义的默认答案来初始化你的mock,这个答案会抛出一个异常,或者更好的是.
例如,你可以在你的测试类中添加下面的类(或者如果你想在其他地方使用它,甚至可以使用前面提到的MockitoConfiguration类,这取决于你想要什么):

static class FailAnswer implements Answer<Object> {

    @Override
    public Object answer(InvocationOnMock invocation) {
        String methodName = invocation.getMethod().getName();
        String className = invocation.getMethod().getDeclaringClass().getSimpleName();
        return fail(String.format("%s#%s should not have been called", className, methodName));
    }
}

字符串
然后在你的setUp方法中使用这个假答案初始化你的mock:

@BeforeEach
    void setUp() {
        delegateService = mock(DelegateService.class, new FailAnswer());
        classUnderTest = new ClassUnderTest(delegateService);
    }


不幸的是,此解决方案与@Mock annotation不兼容,@Mock annotation只接受org.mockito.Answers enum中的本地预定义答案作为参数。因此,这迫使您手动初始化setUp方法中的每个mock,spy,captor(RIP MockitoAnnotations.initMocks(this))
优点:

  • 你摆脱了mockito mock的默认行为,有时隐藏了特定用例中mock的非故意使用(这真的重要吗?)

=>您必须定义您使用的所有内容(在测试或测试夹具中)
=>你不必进行验证来确保你的测试没有调用它不应该调用的方法。
缺点:

  • 这是mockito的一种不寻常的用法,因此这会使您的测试成本更低
  • 你给予MockitoAnnotations功能
  • 当你覆盖mockito的默认配置时,你必须使用配置形式do().when()而不是when(...).do(....),后者提供了不同于前者的类型检查。

警告:这个解决方案并不保证你的mock被调用,它只是保证你没有存根的方法不会被调用。它也不能代替计数方法调用。

c3frrgcw

c3frrgcw3#

我发现的最佳答案是配置Mockito返回SmartNulls
https://static.javadoc.io/org.mockito/mockito-core/2.6.9/org/mockito/Mockito.html#RETURNS_SMART_NULLS
这个实现在处理遗留代码时很有帮助。未存根的方法通常返回null。如果你的代码使用了未存根的调用返回的对象,你会得到一个NullPointerException。Answer的这个实现返回SmartPointer而不是null。SmartPointer给出了比NPE更好的异常消息,因为它指出了未存根的方法被调用的那一行。你只需点击堆栈跟踪。
您可以通过mock或默认方式来实现(可能会导致Spring等其他框架出现问题)。

手动

Writer writerMock = mock(Writer.class, RETURNS_SMART_NULLS);

字符串

标注

@Mock(answer = Answers.RETURNS_SMART_NULLS)

设置为全局默认

配置类必须在这个包中。这可能会导致Spring的奇怪故障。

package org.mockito.configuration;

import org.mockito.internal.stubbing.defaultanswers.ReturnsSmartNulls;
import org.mockito.stubbing.Answer;

public class MockitoConfiguration extends DefaultMockitoConfiguration {

    public Answer<Object> getDefaultAnswer() {
        return new ReturnsSmartNulls();
    }
}


参见:https://solidsoft.wordpress.com/2012/07/02/beyond-the-mockito-refcard-part-1-a-better-error-message-on-npe-with-globally-configured-smartnull/
我在启用全局默认值时遇到了SpringBootRepositories和@MockBean的问题:

java.lang.ClassCastException: org.mockito.codegen.Object$MockitoMock$191495750 cannot be cast to xxx.xxx.MyObject

错误输出示例

org.junit.ComparisonFailure: expected:<[DataRecordType{id=null, name='SomeRecord', pathTemplate='SomeTemplate'}]> but was:<[SmartNull returned by this unstubbed method call on a mock: dataRecordTypeRepository bean.getById(1L);]>

flvlnr44

flvlnr444#

如果您试图跟踪流,您可以使用Mockito验证来检查是否已进行某些调用。

verify(yourMockedObject).yourMethod();

字符串
你也可以用times来验证某个呼叫是否必须被准确地打几次。

verify(yourMockedObject, times(4)).yourMethod();


让单元测试变得复杂并不是一个好的做法。一次只测试一小部分代码。

相关问题