Mockito 匹配器是静态方法和对这些方法的调用,它们在调用 和 期间代表参数。when
verify
Hamcrest 匹配器(存档版本)(或 Hamcrest 样式的匹配器)是无状态的通用对象实例,它们实现并公开一个方法,如果对象与 Matcher 的条件匹配,则该方法将返回 true。它们旨在消除副作用,通常用于如下断言。Matcher<T>
matches(T)
/* Mockito */ verify(foo).setPowerLevel(gt(9000));
/* Hamcrest */ assertThat(foo.getPowerLevel(), is(greaterThan(9000)));
Mockito匹配器存在,与Hamcrest风格的匹配器分开,因此匹配表达式的描述直接适合方法调用:Mockito匹配器返回T
,而Hamcrest匹配器方法返回Matcher对象(类型为Matcher<T>
)。
Mockito 匹配器通过静态方法调用,例如 、、、、on 和 。还有一些适配器,它们在Mockito版本中发生了变化:eq
any
gt
startsWith
org.mockito.Matchers
org.mockito.AdditionalMatchers
- 对于 Mockito 1.x,特色一些调用(如 or )是 Mockito 匹配器,它们直接接受 Hamcrest 匹配器作为参数。
ArgumentMatcher<T>
扩展的,它被用于内部的Hamcrest表示,并且是Hamcrest匹配器基类,而不是任何类型的Mockito匹配器。Matchers
intThat
argThat
org.hamcrest.Matcher<T>
- 对于Mockito 2.0+,Mockito不再直接依赖Hamcrest。 调用短语为或包装
ArgumentMatcher<T>
不再实现但以类似方式使用的对象。诸如 和 的 Hamcrest 适配器仍然可用,但已移至 MockitoHamcrest
。Matchers
intThat
argThat
org.hamcrest.Matcher<T>
argThat
intThat
无论匹配者是Hamcrest还是简单的Hamcrest风格,他们都可以像这样适应:
/* Mockito matcher intThat adapting Hamcrest-style matcher is(greaterThan(...)) */
verify(foo).setPowerLevel(intThat(is(greaterThan(9000))));
在上面的语句中:是一个接受 . 返回 一个 ,它不能用作参数。Mockito匹配器包装该Hamcrest风格的匹配器并返回一个,以便它可以显示为参数;Mockito匹配器喜欢将整个表达式包装到单个调用中,就像示例代码的第一行一样。foo.setPowerLevel
int
is(greaterThan(9000))
Matcher<Integer>
setPowerLevel
intThat
int
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
当您调用类似或(大于)的匹配器时,Mockito 会存储一个匹配器对象,该对象会导致 Mockito 跳过该相等性检查并应用您选择的匹配项。在它存储一个匹配器的情况下,该匹配器保存其参数以供以后检查。any
gt
argumentCaptor.capture()
匹配器返回虚拟值,如零、空集合或 。Mockito 尝试返回一个安全、适当的虚拟值,例如 0 for or 或 的空值。但是,由于类型擦除,Mockito 缺少类型信息来返回除 for 或 以外的任何值,如果尝试“自动取消装箱”基元值,这可能会导致 NullPointerException。null
anyInt()
any(Integer.class)
List<String>
anyListOf(String.class)
null
any()
argThat(...)
null
匹配器喜欢并采用参数值;理想情况下,这些值应在存根/验证开始之前计算。在模拟另一个调用的过程中调用模拟可能会干扰存根。eq
gt
匹配器方法不能用作返回值;例如,没有办法在Mockito中表达或表达。Mockito需要确切地知道在存根调用中返回哪个实例,并且不会为您选择任意返回值。thenReturn(anyInt())
thenReturn(any(Foo.class))
实现细节
匹配器(作为 Hamcrest 样式的对象匹配器)存储在名为 ArgumentMatcherStorage 的类中包含的堆栈中。MockitoCore和Matchers各自拥有一个ThreadSafeMockingProgress实例,该实例静态包含一个ShakeLocal保存MockingProgress实例。正是这个 MockingProgressImpl 包含了一个具体的 ArgumentMatcherStorageImpl。因此,mock 和 matcher 状态是静态的,但在 Mockito 和 Matchers 类之间一致地处于线程范围。
大多数匹配器调用仅添加到此堆栈中,但像 and
、or
和 not
这样的匹配器除外。这完全对应于(并依赖于)Java的评估顺序,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))
- 调用 ,该调用(除非另有存根)返回默认值 。在内部,Mockito标记为最近的呼叫。
foo.quux(0, 0)
false
quux(int, int)
- 调用 ,它放弃其参数并准备在 5 中标识的存根方法。仅有的两个有效状态是堆栈长度为 0(相等)或 2(匹配器),并且堆栈上有两个匹配器(步骤 1 和 4),因此 Mockito 使用匹配器为其第一个参数和第二个参数存根该方法,并清除堆栈。
when(false)
quux(int, int)
any()
and(gt(10), lt(20))
这演示了一些规则:
-
Mockito 无法分辨 和 之间的区别。它们看起来都像是堆栈上有一个int匹配器的调用。因此,如果使用一个匹配器,则必须匹配所有参数。quux(anyInt(), 0)
quux(0, anyInt())
quux(0, 0)
-
呼叫顺序不仅重要,而且是使这一切工作的原因。将匹配器提取到变量通常不起作用,因为它通常会更改调用顺序。但是,将匹配器提取到方法中效果很好。
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或模拟进行交互时检查堆栈,并且必须接受匹配器,而不知道它们是立即使用还是意外放弃。从理论上讲,在调用 或 之外,堆栈应始终为空,但 Mockito 无法自动检查。您可以使用 手动检查。when
verify
Mockito.validateMockitoUsage()
-
在对 的调用中,Mockito 实际上调用了有问题的方法,如果您已将该方法存根以引发异常(或需要非零或非空值),则会引发异常。 和(etc)不调用实际方法,通常是一个有用的替代方案。when
doReturn
doAnswer
-
如果你在存根过程中调用了一个模拟方法(例如,计算匹配器的答案),Mockito会根据该调用检查堆栈长度,并且可能会失败。eq
-
如果你试图做一些不好的事情,比如存根/验证最终方法,Mockito会调用真正的方法,并在堆栈上留下额外的匹配器。方法调用可能不会引发异常,但当您下次与模拟交互时,您可能会从杂散匹配器获得 InvalidUseOfMatchersException。final
常见问题
-
InvalidUseOfMatchersException:
-
带有原始参数的 NullPointerException:返回 null,同时返回 0;这可能会导致 一个,如果你期望一个而不是一个整数。无论如何,首选 ,这将返回零,并且还跳过自动装箱步骤。(Integer) any()
any(Integer.class)
NullPointerException
int
anyInt()
-
NullPointerException 或其他异常:对 will 的调用实际上调用 ,在接收空参数时,您可能已经存根以引发异常。切换到跳过存根行为。when(foo.bar(any())).thenReturn(baz)
foo.bar(null)
doReturn(baz).when(foo).bar(any())
常规故障排除