mockito when() 调用如何工作?
Posted
技术标签:
【中文标题】mockito when() 调用如何工作?【英文标题】:How does mockito when() invocation work? 【发布时间】:2013-01-04 14:29:45 【问题描述】:鉴于以下 Mockito 语句:
when(mock.method()).thenReturn(someValue);
鉴于 mock.method() 语句会将返回值传递给 when(),Mockito 如何为模拟创建代理?我想这使用了一些 CGLib 的东西,但很想知道这在技术上是如何完成的。
【问题讨论】:
【参考方案1】:简短的回答是,在您的示例中,mock.method()
的结果将是一个适合类型的空值; mockito 通过代理、方法拦截和MockingProgress
类的共享实例使用间接寻址,以确定对模拟的方法调用是用于存根还是重放现有存根行为,而不是通过模拟方法的返回值。
在几分钟内查看 mockito 代码的小分析如下。请注意,这是一个非常粗略的描述——这里有很多细节在起作用。我建议您自己查看source on github。
首先,当您使用Mockito
类的mock
方法模拟一个类时,基本上会发生这种情况:
Mockito.mock
代表org.mockito.internal.MockitoCore
.mock,将默认的模拟设置作为参数传递。
MockitoCore.mock
代表org.mockito.internal.util.MockUtil
.createMock
MockUtil
类使用ClassPathLoader
类获取MockMaker
的实例以用于创建模拟。默认情况下,使用 CgLibMockMaker 类。
CgLibMockMaker
使用从 JMock 借来的类 ClassImposterizer
来处理创建模拟。使用的'mockito 魔法'的关键部分是用于创建模拟的MethodInterceptor
:模拟MethodInterceptorFilter
,以及一个MockHandler 实例链,包括MockHandlerImpl 的一个实例。方法拦截器将调用传递给 MockHandlerImpl 实例,该实例实现在模拟上调用方法时应应用的业务逻辑(即,搜索以查看是否已记录答案,确定调用是否代表新的存根等。默认状态是,如果尚未为正在调用的方法注册存根,则返回适合类型的 empty 值。
现在,让我们看看您的示例中的代码:
when(mock.method()).thenReturn(someValue)
这是这段代码的执行顺序:
mock.method()
when(<result of step 1>)
<result of step 2>.thenReturn
了解正在发生的事情的关键是调用 mock 上的方法时会发生什么:方法拦截器被传递有关方法调用的信息,并委托给它的 MockHandler
实例链,最终委托给 @ 987654348@。在MockHandlerImpl#handle
期间,模拟处理程序创建OngoingStubbingImpl
的实例并将其传递给共享的MockingProgress
实例。
在调用method()
之后调用when
方法时,它委托给MockitoCore.when
,后者调用同一类的stub()
方法。此方法从模拟的method()
调用写入的共享MockingProgress
实例中解包正在进行的存根,并将其返回。然后在OngoingStubbing
实例上调用thenReturn
方法。
【讨论】:
感谢您的详细回复。另一个问题-您提到“在调用method()之后调用方法时”-它如何知道when()的调用是下一次调用(或包装)method()的调用?希望这是有道理的。 @marchaos 它不知道。使用when(mock.method()).thenXyz(...)
语法,mock.method()
以“重播”模式执行,而不是“存根”模式。通常,mock.method()
的执行没有任何效果,所以稍后当thenXyz(...)
(thenReturn
、thenThrow
、thenAnswer
等)被执行时,它会进入“存根”模式,然后记录所需的结果该方法调用。
Rogerio,它实际上比这更微妙一些 - mockito 没有明确的存根和重放模式。我稍后会编辑我的答案,以便更清楚。
简而言之,使用 CGLIB 或 Javassist 拦截另一个方法中的方法调用比拦截“if”运算符更容易。
我还没有在这里按摩我的描述,但我也没有忘记它。仅供参考。【参考方案2】:
简短的回答是,在幕后,Mockito 使用某种全局变量/存储来保存方法存根构建步骤的信息(在您的示例中调用 method()、when()、thenReturn()),以便最终,它可以建立一个映射,当在什么参数上调用什么时应该返回什么。
我发现这篇文章很有帮助: 解释基于代理的 Mock 框架如何工作 (http://blog.rseiler.at/2014/06/explanation-how-proxy-based-mock.html)。 作者实现了一个演示 Mocking 框架,我为想要了解这些 Mocking 框架如何工作的人找到了一个非常好的资源。
在我看来,这是 Anti-Pattern 的典型用法。通常我们在实现一个方法时应该避免“副作用”,这意味着该方法应该接受输入并进行一些计算并返回结果——除此之外没有其他改变。但 Mockito 只是故意违反了该规则。它的方法除了返回结果之外还存储了一堆信息:Mockito.anyString()、mockInstance.method()、when()、thenReturn,它们都有特殊的“副作用”。这也是为什么该框架乍一看像魔法一样的原因——我们通常不会编写那样的代码。但是,在 mocking 框架的情况下,这种反模式设计是一个很棒的设计,因为它导致了非常简单的 API。
【讨论】:
优秀的链接。 这背后的天才在于:非常简单的 API 让整个事情看起来非常漂亮。另一个很好的决定是 when() 方法使用泛型,因此 thenReturn() 方法是类型安全的。 我认为这是更好的答案。与其他答案相比,它清楚地解释了模拟过程的概念,而不是通过具体代码的控制流。我同意,很好的链接。以上是关于mockito when() 调用如何工作?的主要内容,如果未能解决你的问题,请参考以下文章
Mockito 异常 - when() 需要一个参数,该参数必须是模拟上的方法调用
Java mockito - 如何在循环中添加return语句?
如何测试 Json.parser 不是用 mockito java 调用的?