gpt4 book ai didi

java - Mockito 匹配器如何工作?

转载 作者:行者123 更新时间:2023-12-01 22:16:17 25 4
gpt4 key购买 nike

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

  • Mockito 匹配器经常导致 InvalidUseOfMatchersException,即使在使用任何匹配器后很长时间执行的代码中也是如此。
  • Mockito 匹配器受制于奇怪的规则,例如,如果给定方法中的一个参数使用匹配器,则只要求对所有参数使用 Mockito 匹配器。
  • Mockito 匹配器在覆盖 Answer 时可能导致 NullPointerException s 或使用时 (Integer) any()
  • 以某些方式使用 Mockito 匹配器重构代码可能会产生异常和意外行为,并且可能完全失败。

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

    最佳答案

    Mockito matchers是静态方法和对这些方法的调用,它们在调用 when 期间代表参数。和 verify .
    Hamcrest matchers (存档版本)(或 Hamcrest 风格的匹配器)是实现 Matcher<T> 的无状态、通用对象实例。并公开一个方法 matches(T)如果对象匹配匹配器的条件,则返回 true。它们旨在没有副作用,并且通常用于断言,例如下面的断言。

    /* Mockito */  verify(foo).setPowerLevel(gt(9000));
    /* Hamcrest */ assertThat(foo.getPowerLevel(), is(greaterThan(9000)));
    Mockito 匹配器存在,与 Hamcrest 风格的匹配器分开,因此匹配表达式的描述直接适合方法调用: Mockito matchers return T where Hamcrest matcher methods return Matcher objects (of type Matcher<T> ).
    Mockito 匹配器通过静态方法调用,例如 eq , any , gt , 和 startsWithorg.mockito.Matchersorg.mockito.AdditionalMatchers .还有适配器,它们在 Mockito 版本之间发生了变化:
  • 对于 Mockito 1.x,Matchers精选一些调用(例如 intThatargThat )是直接接受 Hamcrest 匹配器作为参数的 Mockito 匹配器。 ArgumentMatcher<T> 扩展 org.hamcrest.Matcher<T> ,它用于内部 Hamcrest 表示,是一个 Hamcrest 匹配器基类,而不是任何类型的 Mockito 匹配器。
  • 对于 Mockito 2.0+,Mockito 不再直接依赖于 Hamcrest。 Matchers调用短语为 intThatargThat包装 ArgumentMatcher<T> 不再实现的对象 org.hamcrest.Matcher<T>但以类似的方式使用。 Hamcrest 适配器,例如 argThatintThat仍然可用,但已移至 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))返回 Matcher<Integer> ,这不能用作 setPowerLevel论据。 Mockito 匹配器 intThat包装那个 Hamcrest-style Matcher 并返回一个 int所以它可以作为一个论点出现; Mockito 匹配器喜欢 gt(9000)会将整个表达式包装到单个调用中,如示例代码的第一行。
    匹配器做什么/返回什么
    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
    当你调用像 any 这样的匹配器时或 gt (大于),Mockito 存储一个匹配器对象,该对象使 Mockito 跳过该相等性检查并应用您选择的匹配。在 argumentCaptor.capture()的情况下它存储一个匹配器,用于保存其参数以供以后检查。
    匹配器返回 dummy values例如零、空集合或 null . Mockito 尝试返回一个安全、适当的虚拟值,例如 anyInt() 的值为 0。或 any(Integer.class)或空的 List<String>anyListOf(String.class) .然而,由于类型删除,Mockito 缺乏类型信息来返回任何值,但 nullany()argThat(...) , 如果尝试“自动拆箱” null 可能会导致 NullPointerException原始值(value)。
    匹配器喜欢 eqgt取参数值;理想情况下,应在 stub /验证开始之前计算这些值。在模拟另一个调用的过程中调用模拟会干扰 stub 。
    匹配器方法不能用作返回值;没有办法说 thenReturn(anyInt())thenReturn(any(Foo.class))例如,在 Mockito。 Mockito 需要确切地知道在 stub 调用中返回哪个实例,并且不会为您选择任意的返回值。
    实现细则
    匹配器(作为 Hamcrest 风格的对象匹配器)存储在一个堆栈中,该堆栈包含在一个名为 ArgumentMatcherStorage 的类中。 . MockitoCore 和 Matchers 各有一个 ThreadSafeMockingProgress实例,它静态包含一个 ThreadLocal 持有 MockingProgress 实例。是这个 MockingProgressImpl持有混凝土 ArgumentMatcherStorageImpl .因此,模拟和匹配器状态是静态的,但在 Mockito 和 Matchers 类之间保持一致的线程范围。
    大多数匹配器调用仅添加到此堆栈中,但像 and , or , and not 这样的匹配器除外。 .这完全对应(并依赖于) evaluation order of Java ,它在调用方法之前从左到右计算参数:
    when(foo.quux(anyInt(), and(gt(10), lt(20)))).thenReturn(true);
    [6] [5] [1] [4] [2] [3]
    这将:
  • 添加 anyInt()到堆栈。
  • 添加 gt(10)到堆栈。
  • 添加 lt(20)到堆栈。
  • 删除 gt(10)lt(20)并添加 and(gt(10), lt(20)) .
  • 调用 foo.quux(0, 0) ,它(除非另外 stub )返回默认值 false .内部 Mockito 标记 quux(int, int)作为最近的电话。
  • 调用 when(false) ,丢弃其参数并准备 stub 方法 quux(int, int)在 5 中确定。仅有的两个有效状态是堆栈长度为 0(相等)或 2(匹配器),并且堆栈上有两个匹配器(步骤 1 和 4),因此 Mockito 使用 any() stub 该方法。匹配器的第一个参数和 and(gt(10), lt(20))为它的第二个参数并清除堆栈。

  • 这说明了一些规则:
  • Mockito 无法区分 quux(anyInt(), 0)quux(0, anyInt()) .它们看起来都像是给 quux(0, 0) 的电话堆栈上有一个 int 匹配器。因此,如果您使用一个匹配器,则必须匹配所有参数。
  • 调用顺序不仅重要,而且是使这一切正常工作的原因。将匹配器提取到变量通常不起作用,因为它通常会更改调用顺序。然而,将匹配器提取到方法中效果很好。
    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 或模拟交互时检查堆栈,并且必须在不知道它们是立即使用还是意外放弃的情况下接受匹配器。理论上,在调用 when 之外,堆栈应该始终为空。或 verify ,但 Mockito 无法自动检查。
    您可以使用 Mockito.validateMockitoUsage() 手动检查.
  • 调用 when , Mockito 实际上调用了有问题的方法,如果您已 stub 该方法抛出异常(或需要非零或非空值),则该方法将抛出异常。doReturndoAnswer (etc) 不调用实际方法,通常是一个有用的替代方法。
  • 如果您在 stub 过程中调用了一个模拟方法(例如,计算 eq 匹配器的答案),Mockito 将根据该调用检查堆栈长度,并且可能会失败。
  • 如果你试图做坏事,比如 stubbing/verifying a final method , Mockito 将调用真正的方法并在堆栈上留下额外的匹配器。 final方法调用可能不会抛出异常,但你可能会得到一个 InvalidUseOfMatchersException当您下次与模拟交互时,从流浪匹配器中获取。

  • 常见问题
  • InvalidUseOfMatchersException :
  • 检查每个参数是否只有一个匹配器调用,如果你使用了匹配器,并且你没有在 when 之外使用匹配器或 verify称呼。匹配器永远不应该用作 stub 返回值或字段/变量。
  • 检查您没有调用模拟作为提供匹配器参数的一部分。
  • 检查您是否没有尝试使用匹配器 stub /验证 final方法。这是将匹配器留在堆栈上的好方法,除非您的 final方法抛出异常,否则这可能是您唯一一次意识到您正在模拟的方法是最终的。

  • 带有原始参数的 NullPointerException: (Integer) any() any(Integer.class) 时返回 null返回 0;这可能会导致 NullPointerException如果您期待 int而不是整数。无论如何,首选 anyInt() ,这将返回零并跳过自动装箱步骤。
  • NullPointerException 或其他异常:调用 when(foo.bar(any())).thenReturn(baz)实际上会拨打 foo.bar(null) ,您可能已经在接收空参数时 stub 以抛出异常。切换到 doReturn(baz).when(foo).bar(any()) skips the stubbed behavior .

  • 一般故障排除
  • 使用 MockitoJUnitRunner ,或明确拨打 validateMockitoUsage 在您的 tearDown@After方法(运行者会自动为你做)。这将有助于确定您是否滥用了匹配器。
  • 出于调试目的,添加对 validateMockitoUsage 的调用直接在您的代码中。如果你有任何东西在堆栈上,这将抛出,这是一个不好的症状的好警告。
  • 关于java - Mockito 匹配器如何工作?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/22822512/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com