当前位置: 首页 > 知识库问答 >
问题:

Mockito匹配器是如何工作的?

糜博远
2023-03-14

Mockito参数匹配器(例如anyargthateqsameargumentcaptor.capture())的行为与Hamcrest匹配器非常不同。

>

  • Mockito匹配器经常导致InvalidUseOfMatchersException,即使在使用任何匹配器之后很久才执行的代码中也是如此。

    Mockito匹配器受制于奇怪的规则,例如,只有当给定方法中的一个参数使用匹配器时,才要求对所有参数使用Mockito匹配器。

    Mockito匹配器在重写answers或使用(Integer)any()等时会导致NullPointerException。

    用Mockito匹配器以某些方式重构代码可能会产生异常和意外行为,并可能完全失败。

    为什么Mockito匹配器是这样设计的,它们是如何实现的?

  • 共有1个答案

    钦海荣
    2023-03-14

    Mockito匹配器是静态方法和对这些方法的调用,它们在调用whenverify期间充当参数。

    Hamcrest匹配器(存档版本)(或Hamcrest样式的匹配器)是无状态的通用对象实例,它们实现matcher ,并公开一个方法matches(T),如果对象匹配匹配器的条件,则返回true。它们的目的是没有副作用,通常用于如下断言中。

    /* Mockito */  verify(foo).setPowerLevel(gt(9000));
    /* Hamcrest */ assertThat(foo.getPowerLevel(), is(greaterThan(9000)));
    

    Mockito匹配器与Hamcrest样式的匹配器是分开的,因此匹配表达式的描述直接适合于方法调用:Mockito匹配器返回t,其中Hamcrest匹配器方法返回匹配器对象(类型为matcher )。

    Mockito匹配器通过静态方法调用,例如eqanygtorg.Mockito.matchersorg.Mockito.additionalmatchers上的startswith。还有适配器,它们在Mockito版本中发生了变化:

    • 对于Mockito1.x,matchers具有一些调用(例如intthatargthat)是直接接受Hamcrest matchers作为参数的Mockito matchers。argumentmatcher 扩展了org.Hamcrest.matcher ,它用于内部Hamcrest表示,是Hamcrest matcher基类,而不是任何类型的Mockito matcher。
    • 对于Mockito2.0+,Mockito不再直接依赖于Hamcrest。matchers调用短语为intthatargthatwrapargumentmatcher 的对象,这些对象不再实现org.hamcrest.matcher 但使用方式类似。诸如argthateintthat之类的Hamcrest适配器仍然可用,但已转移到mockitoHamcrest

    无论匹配器是Hamcrest还是简单的Hamcrest风格,它们都可以如下所示进行调整:

    /* Mockito matcher intThat adapting Hamcrest-style matcher is(greaterThan(...)) */
    verify(foo).setPowerLevel(intThat(is(greaterThan(9000))));
    

    在上面的语句中:foo.setPowerLevel是一个接受int的方法。is(greaterThan(9000))返回匹配器 ,该匹配器不能用作setpowerlevel参数。Mockito匹配器intthe包装Hamcrest-style匹配器并返回int,以便它可以作为参数出现;像gt(9000)这样的Mockito匹配器会将整个表达式包装成单个调用,如示例代码的第一行所示。

    when(foo.quux(3, 5)).thenReturn(true);
    

    当不使用参数匹配器时,Mockito会记录参数值,并将其与equals方法进行比较。

    when(foo.quux(eq(3), eq(5))).thenReturn(true);    // same as above
    when(foo.quux(anyInt(), gt(5))).thenReturn(true); // this one's different
    

    eqgt这样的匹配器接受参数值;理想情况下,这些值应该在stubbing/验证开始之前计算。在嘲笑另一个调用的中间调用一个嘲笑可能会干扰存根。

    匹配器方法不能用作返回值;例如,在Mockito中,无法将thenreturn(anyInt())thenreturn(any(foo.class))短语化。Mockito需要确切地知道在stubbing调用中返回哪个实例,并且不会为您选择任意的返回值。

    匹配器存储在一个称为ArgumentMatcherStorage的类中的堆栈中(作为HamCrest样式的对象匹配器)。MockitoCore和Matchers各自拥有一个ThreadSafeMockingProgress实例,该实例静态地包含一个ThreadLocal保存MockingProgress实例。正是这个MockingProgressImpl持有一个具体的论证MatcherStorageImpl。因此,mock和matcher状态是静态的,但在Mockito和Matchers类之间线程范围一致。

    大多数匹配器调用只添加到这个堆栈中,但以及等匹配器例外。这完全符合(并依赖于)Java的计算顺序,该顺序在调用方法之前从左到右计算参数:

    when(foo.quux(anyInt(), and(gt(10), lt(20)))).thenReturn(true);
    [6]      [5]  [1]       [4] [2]     [3]
    

    这将:

      null

    呼叫命令不仅重要,而且是让这一切工作的原因。将匹配器提取到变量一般不起作用,因为它通常会改变调用顺序。然而,将匹配器提取到方法非常有效。

    int between10And20 = and(gt(10), lt(20));
    /* BAD */ when(foo.quux(anyInt(), between10And20)).thenReturn(true);
    // Mockito sees the stack as the opposite: and(gt(10), lt(20)), anyInt().
    
    public static int anyIntBetween10And20() { return and(gt(10), lt(20)); }
    /* OK */  when(foo.quux(anyInt(), anyIntBetween10And20())).thenReturn(true);
    // The helper method calls the matcher methods in the right order.
    

    堆栈经常变化,以至于Mockito不能非常仔细地监控它。它只能在与Mockito或mock交互时检查堆栈,并且必须接受匹配器,而不知道它们是立即使用还是意外放弃。理论上,当或验证时,堆栈在调用之外应该总是空的,但是Mockito不能自动检查这一点。您可以使用mockito.validateMockitousage()手动检查。

    在对when的调用中,Mockito实际上调用了有问题的方法,如果您为抛出异常(或者需要非零或非NULL值)而对该方法进行了截断,则该方法将抛出异常doReturndoAnswer(etc)不调用实际的方法,通常是一种有用的替代方法。

    如果您在stubbing过程中调用了一个mock方法(例如,为eq匹配器计算答案),Mockito将根据该调用检查堆栈长度,并且很可能失败。

    如果您试图做一些不好的事情,比如stubbing/verification final方法,Mockito将调用real方法,并在堆栈中留下额外的匹配器。final方法调用可能不会引发异常,但在下次与模拟交互时,您可能会从杂散匹配器中获得InvalidUseOfMatchersException。

    >

  • invaliduseofmatchersexception:

    >

  • 检查是否每个参数都有一个匹配器调用(如果您使用匹配器的话),并且是否在WhenVerify调用之外使用过匹配器。匹配器永远不应该用作stubbed返回值或字段/变量。

    带有原语参数的NullPointerException:(Integer)any()返回null,而any(Integer.class)返回0;如果您期望的是int而不是整数,这可能会导致NullPointerException。在任何情况下,首选anyint(),它将返回零并跳过自动装箱步骤。

    NullPointerException或其他异常:调用when(foo.bar(any())).thenreturn(baz)实际上将调用foo.bar(null),在接收到空参数时,您可能已经将其剪断以引发异常。切换到doreturn(baz).when(foo).bar(any())跳过stubbed行为。

    >

  • 使用MockitoJUnitRunner,或者在teardown@after方法中显式调用validateMockitousage(运行程序将自动为您执行此操作)。这将有助于确定您是否滥用了匹配器。

    出于调试目的,请直接在代码中添加对ValidateMockitousage的调用。如果堆栈上有任何东西,这将抛出,这是不良症状的良好警告。

  •  类似资料:
    • 问题内容: 争论的Mockito匹配器(如,,,,和)从Hamcrest匹配器表现非常不同。 匹配器经常导致,即使在使用任何匹配器很长时间后执行的代码中也是如此。 匹配器遵循怪异的规则,例如,如果给定方法中的一个参数使用匹配器,则仅要求对所有参数使用Mockito匹配器。 当覆盖或使用时,匹配器可能导致 。 使用Mockito匹配器以某些方式重构代码会产生异常和意外行为,并且可能会完全失败。 为什

    • 鉴于以下Mockito语句: 假设mock.method()语句将返回值传递给when(),Mockito如何为mock创建代理?我想这使用了一些CGLib的东西,但我想知道这是如何在技术上完成的。

    • 我是Elasticsearch新手,对匹配查询的工作方式感到困惑。我有以下映射: 我批量导入了以下文档 我验证了所有文档都已成功加载。然后我执行匹配查询: 它只返回#1文档。我在这里感到困惑。为什么它不返回所有三个文档?我应该使用什么查询来返回这三个文档,因为它们在字段中都有“quiet”根单词? 谢谢和问候。

    • 问题内容: 我有这样声明的方法 这个枚举 问题:如何模拟通话?我无法比拟。 以下无效: 问题答案: 将达到目的: 附带说明:考虑使用静态导入: 模拟变得更短:

    • 问题内容: 使用Mockito,我想在其参数列表中使用方法调用,但是我没有找到如何编写该方法的方法。 我只想要像Mockito那样的东西? 问题答案: 我会尝试

    • 使用Mockito,我想一个参数列表中包含的方法调用,但我没有找到如何编写这一点。 我只想要类似于的东西,如何使用Mockito实现这一点?