Answers:
简短的答案是,在您的示例中,的结果mock.method()
将是类型合适的空值;mockito通过代理,方法拦截和类的共享实例使用间接方式,MockingProgress
以确定对模拟方法的调用是用于存根还是重放现有的存根行为,而不是通过的返回值传递有关存根的信息。一种嘲笑的方法。
几分钟后,对微型代码进行了简短的分析。注意,这是一个非常粗糙的描述-这里有很多细节。我建议您自己检查github上的源代码。
首先,当您使用类的mock
方法模拟一个类时Mockito
,实际上是这样:
Mockito.mock
将org.mockito.internal.MockitoCore
默认的模拟设置作为参数传递给.mock。MockitoCore.mock
代表org.mockito.internal.util.MockUtil
.createMockMockUtil
类使用ClassPathLoader
类来获得的实例MockMaker
用于创建模拟。默认情况下,使用CgLibMockMaker类。CgLibMockMaker
使用从JMock借用的类,该类ClassImposterizer
处理创建模拟。所使用的“魔术的Mockito”键片的MethodInterceptor
用于创建模拟:所述的Mockito MethodInterceptorFilter
以及MockHandler实例,包括的实例的链MockHandlerImpl。方法拦截器将调用传递给MockHandlerImpl实例,该实例实现在对某个模拟调用某个方法时(例如,搜索是否已记录答案,确定该调用是否代表新的存根等)应应用的业务逻辑。默认状态是,如果尚未为正在调用的方法注册存根,则将返回类型合适的空值。现在,让我们看一下示例中的代码:
when(mock.method()).thenReturn(someValue)
这是此代码执行的顺序:
mock.method()
when(<result of step 1>)
<result of step 2>.thenReturn
理解发生了什么的关键是当调用模拟方法上的方法时会发生什么:向方法拦截器传递有关方法调用的信息,并委托给其MockHandler
实例链,最终将实例委托给MockHandlerImpl#handle
。在期间MockHandlerImpl#handle
,模拟处理程序创建的实例OngoingStubbingImpl
并将其传递给共享MockingProgress
实例。
在when
调用之后调用该方法时method()
,它将委托给MockitoCore.when
,后者将调用stub()
同一类的方法。此方法从MockingProgress
模拟method()
调用写入的共享实例中解压缩正在进行的存根,然后将其返回。然后thenReturn
在OngoingStubbing
实例上调用方法。
when(mock.method()).thenXyz(...)
语法,mock.method()
将在“重放”模式下执行,而不是在“存根”模式下执行。通常情况下,该执行的mock.method()
没有效果,所以后来当thenXyz(...)
(thenReturn
,thenThrow
,thenAnswer
得到执行,等等),它进入“磕碰”模式,并且然后记录该方法调用所期望的结果。
简短的答案是,在后台,Mockito使用某种全局变量/存储来保存方法存根构建步骤的信息(在您的示例中调用method(),when()和thenReturn()),以便最终可以建立关于在什么参数上调用什么应该返回什么的映射。
我发现这篇文章非常有帮助: 解释了基于代理的Mock框架的工作原理(http://blog.rseiler.at/2014/06/explanation-how-proxy-based-mock.html)。作者实现了一个示范的Mocking框架,对于那些想弄清楚这些Mocking框架如何工作的人,我发现了一个很好的资源。
在我看来,这是反模式的典型用法。通常,我们在实现方法时应避免“副作用”,这意味着该方法应接受输入并进行一些计算并返回结果-除此之外,没有其他更改。但是Mockito只是故意违反了该规则。它的方法除了返回结果外,还存储大量信息:Mockito.anyString(),mockInstance.method(),when(),thenReturn,它们都有特殊的“副作用”。这就是框架乍一看看起来像魔术的原因-我们通常不写那样的代码。但是,在模拟框架的情况下,这种反模式设计是一个很好的设计,因为它可以导致非常简单的API。