当进行意外调用时,如何让Mockito mocks导致失败



我有一些mock对象,它们可能会被传递一些,最终可能会变得相当复杂。

我想让Mockito为对mock的每次调用输出一个日志,或者每当进行意外调用时,我希望它失败,这样我就可以迭代这些调用并设置适当的响应。

我怎样才能做到这一点?

最常用的方法是使用verifyNoMoreInteractions,如Mockito文档#8:

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

我在上面说"最惯用"是因为这种方法有自己的警告标签,链接到Mockito创始人Szczepan Faber的博客文章"我应该担心意外吗?"。

不建议在所有测试方法中使用

verifyNoMoreInteractions()verifyNoMoreInteractions()是来自交互测试工具包的一个方便的断言。只有在相关的时候才使用它。滥用它会导致过度指定、维护性较差测试。

简而言之,您应该有一个非常明确的理由来检查您的依赖项没有执行,或者您的测试系统不调用的情况,而不是他们正在执行和调用的情况。如果您想避免不必要的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的开发人员可能会说,这种容易出现异常的治疗方法比疾病更糟糕,可能只对临时诊断最复杂的遗留代码有用。

我只是问自己同样的问题。。。使用ReturnsSmartNulls的解决方案将返回SmartNulls而不是null。。。所以它对非空方法是有意义的,对吗?无效的方法呢,那些有副作用的方法呢?

在我看来,如果你想确保当你的mock的一个方法在没有你的显式行为定义的情况下被调用时,你的测试失败(doXXX(…).when(…)mockito方法),你可以用一个自定义的默认答案来初始化你的mocks,这个答案会抛出一个异常,或者更好。。。考试不及格。

例如,您可以在测试类中添加以下类(如果您打算在其他地方使用它,则可以在外部添加,甚至可以根据您的需要使用前面提到的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));
}
}

然后在你的设置方法中用这个假答案初始化你的mock:

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

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

优点:

  • 您取消了mockito mock的默认行为,有时会为特定的用例隐藏mock的非常规使用(这真的很重要吗?)
    =>您必须定义您所使用的一切(在测试或测试设备内部)
    =>您不必进行验证以确保您的测试没有调用它不应该调用的方法

缺点:

  • 这是mockito的一个不寻常的用法,因此这会降低您的测试成本
  • 你放弃了MockitoAnnotations功能
  • 当您覆盖mockito默认的存根时,您必须使用存根形式do().when(),而不是when(…).do(…),后者提供与前者不同的au类型检查

警告:此解决方案不会保证调用mock,它只是保证不会调用未存根的方法。它也不能代替计数方法调用。

我找到的最佳答案是将Mockito配置为返回SmartNulls

https://static.javadoc.io/org.mockito/mockito-core/2.6.9/org/mockito/Mockito.html#RETURNS_SMART_NULLS

当使用遗留代码时,此实现可能会有所帮助。未缓冲的方法通常返回null。如果您的代码使用由未插入调用返回的对象,则会得到NullPointerException。Answer的此实现返回SmartNull而不是null。SmartNull提供了比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/

在启用全局默认值时,我遇到了SpringBootRepository和@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);]>

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

verify(yourMockedObject).yourMethod();

您还可以使用时间来验证某个呼叫是否必须准确地进行一定次数。

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

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

相关内容

  • 没有找到相关文章

最新更新