Java Mockito 匹配器如何工作?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/22822512/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How do Mockito matchers work?
提问by Jeff Bowman
Mockito argument matchers (such as any
, argThat
, eq
, same
, and ArgumentCaptor.capture()
) behave very differently from Hamcrest matchers.
Mockito 参数匹配器(例如any
, argThat
, eq
, same
, 和ArgumentCaptor.capture()
)的行为与 Hamcrest 匹配器非常不同。
Mockito matchers frequently cause InvalidUseOfMatchersException, even in code that executes long after any matchers were used.
Mockito matchers are beholden to weird rules, such as only requiring the use of Mockito matchers for all arguments if one argument in a given method uses a matcher.
Mockito matchers can cause NullPointerException when overriding
Answer
s or when using(Integer) any()
etc.Refactoring code with Mockito matchers in certain ways can produce exceptions and unexpected behavior, and may fail entirely.
Mockito 匹配器经常导致 InvalidUseOfMatchersException,即使在使用任何匹配器后很长时间执行的代码中也是如此。
Mockito 匹配器受制于奇怪的规则,例如,如果给定方法中的一个参数使用匹配器,则只要求对所有参数使用 Mockito 匹配器。
Mockito 匹配器在覆盖
Answer
s 或使用(Integer) any()
等时可能会导致 NullPointerException 。以某些方式使用 Mockito 匹配器重构代码可能会产生异常和意外行为,并且可能完全失败。
Why are Mockito matchers designed like this, and how are they implemented?
为什么 Mockito 匹配器是这样设计的,它们是如何实现的?
采纳答案by Jeff Bowman
Mockito matchersare static methods and calls to those methods, which stand in for argumentsduring calls to when
and verify
.
Mockito 匹配器是静态方法和对这些方法的调用,它们在调用和期间代表参数。when
verify
Hamcrest matchers(archived version) (or Hamcrest-style matchers) are stateless, general-purpose object instances that implement Matcher<T>
and expose a method matches(T)
that returns true if the object matches the Matcher's criteria. They are intended to be free of side effects, and are generally used in assertions such as the one below.
Hamcrest 匹配器(存档版本)(或 Hamcrest 风格的匹配器)是无状态的通用对象实例,它实现Matcher<T>
并公开一个方法matches(T)
,如果对象与匹配器的标准匹配,则该方法返回 true。它们旨在没有副作用,并且通常用于断言,例如下面的断言。
/* Mockito */ verify(foo).setPowerLevel(gt(9000));
/* Hamcrest */ assertThat(foo.getPowerLevel(), is(greaterThan(9000)));
Mockito matchers exist, separate from Hamcrest-style matchers, so that descriptions of matching expressions fit directly into method invocations: Mockito matchers return T
where Hamcrest matcher methods return Matcher objects (of type Matcher<T>
).
Mockito 匹配器存在,与 Hamcrest 样式匹配器分开,因此匹配表达式的描述直接适合方法调用:Mockito 匹配器返回T
,Hamcrest 匹配器方法返回 Matcher 对象(类型Matcher<T>
)。
Mockito matchers are invoked through static methods such as eq
, any
, gt
, and startsWith
on org.mockito.Matchers
and org.mockito.AdditionalMatchers
. There are also adapters, which have changed across Mockito versions:
匹配器的Mockito通过静态方法,如调用eq
,any
,gt
,和startsWith
上org.mockito.Matchers
和org.mockito.AdditionalMatchers
。还有适配器,它们在 Mockito 版本之间发生了变化:
- For Mockito 1.x,
Matchers
featured some calls (such asintThat
orargThat
) are Mockito matchers that directly accept Hamcrest matchers as parameters.ArgumentMatcher<T>
extendedorg.hamcrest.Matcher<T>
, which was used in the internal Hamcrest representation and was a Hamcrest matcher base classinstead of any sort of Mockito matcher. - For Mockito 2.0+, Mockito no longer has a direct dependency on Hamcrest.
Matchers
calls phrased asintThat
orargThat
wrapArgumentMatcher<T>
objects that no longer implementorg.hamcrest.Matcher<T>
but are used in similar ways. Hamcrest adapters such asargThat
andintThat
are still available, but have moved toMockitoHamcrest
instead.
- 对于 Mockito 1.x,
Matchers
某些调用(例如intThat
或argThat
)是直接接受 Hamcrest 匹配器作为参数的 Mockito 匹配器。ArgumentMatcher<T>
extendedorg.hamcrest.Matcher<T>
,它用于内部 Hamcrest 表示,是一个Hamcrest 匹配器基类,而不是任何类型的 Mockito 匹配器。 - 对于 Mockito 2.0+,Mockito 不再直接依赖于 Hamcrest。
Matchers
称为intThat
或argThat
包装ArgumentMatcher<T>
不再实现org.hamcrest.Matcher<T>
但以类似方式使用的对象的调用。Hamcrest 适配器如argThat
和intThat
仍然可用,但已MockitoHamcrest
改为使用。
Regardless of whether the matchers are Hamcrest or simply Hamcrest-style, they can be adapted like so:
无论匹配器是 Hamcrest 还是 Hamcrest 风格,它们都可以像这样进行调整:
/* Mockito matcher intThat adapting Hamcrest-style matcher is(greaterThan(...)) */
verify(foo).setPowerLevel(intThat(is(greaterThan(9000))));
In the above statement: foo.setPowerLevel
is a method that accepts an int
. is(greaterThan(9000))
returns a Matcher<Integer>
, which wouldn't work as a setPowerLevel
argument. The Mockito matcher intThat
wraps that Hamcrest-style Matcher and returns an int
so it canappear as an argument; Mockito matchers like gt(9000)
would wrap that entire expression into a single call, as in the first line of example code.
在上面的语句中:foo.setPowerLevel
是一个接受int
. is(greaterThan(9000))
返回 a Matcher<Integer>
,它不能作为setPowerLevel
参数工作。Mockito 匹配器intThat
包装了 Hamcrest 风格的匹配器并返回一个,int
以便它可以作为参数出现;Mockito 匹配器喜欢gt(9000)
将整个表达式包装成单个调用,如示例代码的第一行。
What matchers do/return
匹配器做什么/返回什么
when(foo.quux(3, 5)).thenReturn(true);
When not using argument matchers, Mockito records your argument values and compares them with their equals
methods.
当不使用参数匹配器时,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
When you call a matcher like any
or gt
(greater than), Mockito stores a matcher object that causes Mockito to skip that equality check and apply your match of choice. In the case of argumentCaptor.capture()
it stores a matcher that saves its argument instead for later inspection.
当你调用一个像any
或gt
(大于)匹配器时,Mockito 存储一个匹配器对象,该对象导致 Mockito 跳过该相等性检查并应用你选择的匹配。在这种情况下,argumentCaptor.capture()
它存储一个匹配器,该匹配器保存其参数以供以后检查。
Matchers return dummy valuessuch as zero, empty collections, or null
. Mockito tries to return a safe, appropriate dummy value, like 0 for anyInt()
or any(Integer.class)
or an empty List<String>
for anyListOf(String.class)
. Because of type erasure, though, Mockito lacks type information to return any value but null
for any()
or argThat(...)
, which can cause a NullPointerException if trying to "auto-unbox" a null
primitive value.
匹配器返回虚拟值,例如零、空集合或null
. Mockito 尝试返回一个安全、适当的虚拟值,例如 0 代表anyInt()
或any(Integer.class)
或空的List<String>
for anyListOf(String.class)
。但是,由于类型擦除,Mockito 缺少类型信息来返回除null
forany()
或之外的任何值argThat(...)
,如果尝试“自动拆箱”null
原始值,这可能会导致 NullPointerException 。
Matchers like eq
and gt
take parameter values; ideally, these values should be computed before the stubbing/verification starts. Calling a mock in the middle of mocking another call can interfere with stubbing.
匹配器喜欢eq
并gt
接受参数值;理想情况下,应在存根/验证开始之前计算这些值。在模拟另一个调用的过程中调用模拟会干扰存根。
Matcher methods can't be used as return values; there is no way to phrase thenReturn(anyInt())
or thenReturn(any(Foo.class))
in Mockito, for instance. Mockito needs to know exactly which instance to return in stubbing calls, and will not choose an arbitrary return value for you.
匹配器方法不能用作返回值;例如,无法用 Mockito来表达thenReturn(anyInt())
或thenReturn(any(Foo.class))
表达。Mockito 需要确切地知道在存根调用中返回哪个实例,并且不会为您选择任意的返回值。
Implementation details
实施细则
Matchers are stored (as Hamcrest-style object matchers) in a stack contained in a class called ArgumentMatcherStorage. MockitoCore and Matchers each own a ThreadSafeMockingProgressinstance, which staticallycontains a ThreadLocal holding MockingProgress instances. It's this MockingProgressImplthat holds a concrete ArgumentMatcherStorageImpl. Consequently, mock and matcher state is static but thread-scoped consistently between the Mockito and Matchers classes.
匹配器(作为 Hamcrest 风格的对象匹配器)存储在一个名为ArgumentMatcherStorage的类中的堆栈中。MockitoCore 和 Matchers 各自拥有一个ThreadSafeMockingProgress实例,该实例静态包含一个 ThreadLocal 持有 MockingProgress 实例。正是这个MockingProgressImpl持有一个具体的ArgumentMatcherStorageImpl。因此,模拟和匹配器状态是静态的,但在 Mockito 和 Matchers 类之间保持一致的线程范围。
Most matcher calls only add to this stack, with an exception for matchers like and
, or
, and not
. This perfectly corresponds to (and relies on) the evaluation order of Java, which evaluates arguments left-to-right before invoking a method:
最匹配的呼叫只会增加这个堆栈,与喜欢的匹配例外and
,or
和not
。这完全对应(并依赖于)Java的评估顺序,它在调用方法之前从左到右评估参数:
when(foo.quux(anyInt(), and(gt(10), lt(20)))).thenReturn(true);
[6] [5] [1] [4] [2] [3]
This will:
这会:
- Add
anyInt()
to the stack. - Add
gt(10)
to the stack. - Add
lt(20)
to the stack. - Remove
gt(10)
andlt(20)
and addand(gt(10), lt(20))
. - Call
foo.quux(0, 0)
, which (unless otherwise stubbed) returns the default valuefalse
. Internally Mockito marksquux(int, int)
as the most recent call. - Call
when(false)
, which discards its argument and prepares to stub methodquux(int, int)
identified in 5. The only two valid states are with stack length 0 (equality) or 2 (matchers), and there are two matchers on the stack (steps 1 and 4), so Mockito stubs the method with anany()
matcher for its first argument andand(gt(10), lt(20))
for its second argument and clears the stack.
- 添加
anyInt()
到堆栈中。 - 添加
gt(10)
到堆栈中。 - 添加
lt(20)
到堆栈中。 - 删除
gt(10)
和lt(20)
添加and(gt(10), lt(20))
。 - Call
foo.quux(0, 0)
,它(除非以其他方式存根)返回默认值false
。Mockitoquux(int, int)
在内部标记为最近的调用。 - Call
when(false)
,它丢弃其参数并准备存根quux(int, int)
5 中标识的方法。仅有的两个有效状态是堆栈长度为 0(相等)或 2(匹配器),并且堆栈上有两个匹配器(步骤 1 和 4),因此Mockito 使用any()
匹配器为其第一个参数和and(gt(10), lt(20))
第二个参数存根该方法并清除堆栈。
This demonstrates a few rules:
这说明了一些规则:
Mockito can't tell the difference between
quux(anyInt(), 0)
andquux(0, anyInt())
. They both look like a call toquux(0, 0)
with one int matcher on the stack. Consequently, if you use one matcher, you have to match all arguments.Call order isn't just important, it's what makes this all work. Extracting matchers to variables generally doesn't work, because it usually changes the call order. Extracting matchers to methods, however, works great.
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.
The stack changes often enough that Mockito can't police it very carefully. It can only check the stack when you interact with Mockito or a mock, and has to accept matchers without knowing whether they're used immediately or abandoned accidentally. In theory, the stack should always be empty outside of a call to
when
orverify
, but Mockito can't check that automatically. You can check manually withMockito.validateMockitoUsage()
.In a call to
when
, Mockito actually calls the method in question, which will throw an exception if you've stubbed the method to throw an exception (or require non-zero or non-null values).doReturn
anddoAnswer
(etc) do notinvoke the actual method and are often a useful alternative.If you had called a mock method in the middle of stubbing (e.g. to calculate an answer for an
eq
matcher), Mockito would check the stack length against thatcall instead, and likely fail.If you try to do something bad, like stubbing/verifying a final method, Mockito will call the real method and also leave extra matchers on the stack. The
final
method call may not throw an exception, but you may get an InvalidUseOfMatchersExceptionfrom the stray matchers when you next interact with a mock.
可以的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
or之外堆栈应该始终为空verify
,但 Mockito 无法自动检查。您可以使用 手动检查Mockito.validateMockitoUsage()
。在对 的调用中
when
,Mockito 实际上调用了有问题的方法,如果您已存根方法抛出异常(或需要非零或非空值),则该方法将抛出异常。doReturn
anddoAnswer
(etc)不调用实际方法,通常是有用的替代方法。如果您在存根过程中调用了一个模拟方法(例如计算
eq
匹配器的答案),Mockito 将根据该调用检查堆栈长度,并且可能会失败。如果你试图做一些不好的事情,比如存根/验证最终方法,Mockito 将调用真正的方法,并在堆栈上留下额外的匹配器。该
final
方法调用可能不会抛出异常,但你可能会得到一个InvalidUseOfMatchersException从流浪的匹配,当你下一次交互使用模拟。
Common problems
常见问题
InvalidUseOfMatchersException:
Check that every single argument has exactly one matcher call, if you use matchers at all, and that you haven't used a matcher outside of a
when
orverify
call. Matchers should never be used as stubbed return values or fields/variables.Check that you're not calling a mock as a part of providing a matcher argument.
Check that you're not trying to stub/verify a final method with a matcher. It's a great way to leave a matcher on the stack, and unless your final method throws an exception, this might be the only time you realize the method you're mocking is final.
NullPointerException with primitive arguments:
(Integer) any()
returns null whileany(Integer.class)
returns 0; this can cause aNullPointerException
if you're expecting anint
instead of an Integer. In any case, preferanyInt()
, which will return zero and also skip the auto-boxing step.NullPointerException or other exceptions:Calls to
when(foo.bar(any())).thenReturn(baz)
will actually callfoo.bar(null)
, which you might have stubbed to throw an exception when receiving a null argument. Switching todoReturn(baz).when(foo).bar(any())
skips the stubbed behavior.
InvalidUseOfMatchersException:
检查每个参数是否只有一个匹配器调用,如果您根本使用匹配器,并且您没有在
when
orverify
调用之外使用匹配器。匹配器永远不应该用作存根返回值或字段/变量。检查您没有调用模拟作为提供匹配器参数的一部分。
检查您是否没有尝试使用匹配器存根/验证最终方法。这是将匹配器留在堆栈上的好方法,除非您的最终方法抛出异常,否则这可能是您唯一一次意识到您正在模拟的方法是最终的。
带有原始参数的 NullPointerException:
(Integer) any()
返回 null 而any(Integer.class)
返回 0;NullPointerException
如果您期望的是一个int
而不是一个整数,这可能会导致 a 。在任何情况下,都使用 preferredanyInt()
,它将返回零并跳过自动装箱步骤。NullPointerException 或其他异常:调用
when(foo.bar(any())).thenReturn(baz)
将实际调用foo.bar(null)
,您可能在接收空参数时存根以抛出异常。切换到doReturn(baz).when(foo).bar(any())
跳过存根行为。
General troubleshooting
一般故障排除
Use MockitoJUnitRunner, or explicitly call
validateMockitoUsage
in yourtearDown
or@After
method (which the runner would do for you automatically). This will help determine whether you've misused matchers.For debugging purposes, add calls to
validateMockitoUsage
in your code directly. This will throw if you have anything on the stack, which is a good warning of a bad symptom.
使用MockitoJUnitRunner,或显式调用
validateMockitoUsage
您的tearDown
or@After
方法(运行程序会自动为您执行)。这将有助于确定您是否滥用了匹配器。出于调试目的,
validateMockitoUsage
请直接在代码中添加对 的调用。如果堆栈中有任何内容,这将抛出,这是对不良症状的一个很好的警告。
回答by tibtof
Just a small addition to Jeff Bowman's excellent answer, as I found this question when searching for a solution to one of my own problems:
只是杰夫鲍曼出色答案的一小部分,因为我在寻找解决自己问题的方法时发现了这个问题:
If a call to a method matches more than one mock's when
trained calls, the order of the when
calls is important, and should be from the most wider to the most specific. Starting from one of Jeff's examples:
如果对方法的调用与多个模拟的when
训练调用匹配,则调用的顺序when
很重要,并且应该从最广泛到最具体。从杰夫的例子之一开始:
when(foo.quux(anyInt(), anyInt())).thenReturn(true);
when(foo.quux(anyInt(), eq(5))).thenReturn(false);
is the order that ensures the (probably) desired result:
是确保(可能)所需结果的顺序:
foo.quux(3 /*any int*/, 8 /*any other int than 5*/) //returns true
foo.quux(2 /*any int*/, 5) //returns false
If you inverse the when calls then the result would always be true
.
如果您反转 when 调用,则结果将始终为true
.