鉴于以下 Mockito 声明:
when(mock.method()).thenReturn(someValue);
假设 mock.method() 语句将返回值传递给 when(),Mockito 如何为模拟创建代理?我想这使用了一些 CGLib 的东西,但很想知道这在技术上是如何完成的。
鉴于以下 Mockito 声明:
when(mock.method()).thenReturn(someValue);
假设 mock.method() 语句将返回值传递给 when(),Mockito 如何为模拟创建代理?我想这使用了一些 CGLib 的东西,但很想知道这在技术上是如何完成的。
简短的回答是,在您的示例中,结果mock.method()
将是一个适合类型的空值;mockito 通过代理、方法拦截和MockingProgress
类的共享实例来使用间接方法,以确定对 mock 的方法调用是用于存根还是重放现有存根行为,而不是通过返回值传递有关存根的信息一个模拟的方法。
在几分钟内查看 mockito 代码的小分析如下。请注意,这是一个非常粗略的描述——这里有很多细节在起作用。我建议你自己查看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
用于创建模拟:mockitoMethodInterceptorFilter
和 MockHandler 实例链,包括 MockHandlerImpl 的实例。方法拦截器将调用传递给 MockHandlerImpl 实例,该实例实现在模拟上调用方法时应应用的业务逻辑(即,搜索以查看是否已记录答案,确定调用是否代表新的存根等。默认状态是,如果尚未为正在调用的方法注册存根,则返回一个适合类型的空值。现在,让我们看一下示例中的代码:
when(mock.method()).thenReturn(someValue)
以下是此代码的执行顺序:
mock.method()
when(<result of step 1>)
<result of step 2>.thenReturn
了解正在发生的事情的关键是调用 mock 上的方法时会发生什么:方法拦截器被传递有关方法调用的信息,并委托给它的MockHandler
实例链,最终委托给MockHandlerImpl#handle
. 在 期间MockHandlerImpl#handle
,模拟处理程序创建一个实例OngoingStubbingImpl
并将其传递给共享MockingProgress
实例。
在when
调用 之后调用该方法时method()
,它会委托给MockitoCore.when
,后者调用stub()
同一类的方法。此方法从模拟调用写入的共享MockingProgress
实例中解包正在进行的存根,并将其返回。method()
然后在实例thenReturn
上调用方法。OngoingStubbing
简短的回答是,在幕后,Mockito 使用某种全局变量/存储来保存方法存根构建步骤的信息(在您的示例中调用 method()、when()、thenReturn()),以便最终它可以在什么参数上调用什么时应该返回什么建立一个映射。
我发现这篇文章很有帮助: Explanation how proxy based Mock Frameworks work ( 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。