mockito when()调用如何工作?

鉴于以下Mockito声明:

when(mock.method()).thenReturn(someValue); 

假定mock.method()语句将返回值传递给when(),Mockito如何为模拟创build一个代理对象? 我想这是使用一些CGLib的东西,但会有兴趣知道这是如何在技术上完成的。

简短的回答是,在你的例子中, mock.method()的结果将是一个types适当的空值; mockito通过代理,方法拦截和MockingProgress类的共享实例间接使用间接方法,以确定模拟方法上的方法的调用是否用于存根或重播现有的存根行为,而不是通过返回值传递关于存根的信息一个嘲弄的方法。

在几分钟内看一下mockito代码的一个迷你分析如下。 请注意,这是一个非常粗略的描述 – 这里有很多细节。 我build议你自己检查一下github上的源代码 。

首先,当你使用Mockito类的mock方法来模拟一个类时,这实际上是发生了什么事情:

  1. Mockito.mock委托给org.mockito.internal.MockitoCore .mock,传递默认的模拟设置作为参数。
  2. MockitoCore.mock委托给org.mockito.internal.util.MockUtil .createMock
  3. MockUtil类使用ClassPathLoader类来获取MockMaker一个实例来创build模拟。 默认情况下,使用CgLibMockMaker类。
  4. CgLibMockMaker使用从JMock借用的类, ClassImposterizer处理创build模拟。 使用的“mockito魔术”的关键部分是用于创build模拟的MethodInterceptorFilter :mockito MethodInterceptorFilter和一系列MockHandler实例,包括MockHandlerImpl的一个实例。 方法拦截器将调用传递给MockHandlerImpl实例,MockHandlerImpl实例实现了在模拟上调用某个方法时应该应用的业务逻辑(即,查找是否已经logging了答案,确定调用是否代表新的存根等)。默认状态是,如果存根尚未被调用的方法注册,则返回一个types适当的值。

现在,让我们看看你的例子中的代码:

 when(mock.method()).thenReturn(someValue) 

这里是这个代码的执行顺序:

  1. mock.method()
  2. when(<result of step 1>)
  3. <result of step 2>.thenReturn

理解发生什么的关键是调用模拟方法时发生的情况:方法拦截器传递关于方法调用的信息,并委托给其最终委托给MockHandlerImpl#handleMockHandler实例链。 在MockHandlerImpl#handle ,模拟处理程序创build一个OngoingStubbingImpl实例,并将其传递给共享的MockingProgress实例。

当调用method()后调用when方法when ,它将委托给调用同一类的stub()方法的MockitoCore.when 。 这个方法从正在调用的method()调用写入的共享MockingProgress实例中MockingProgress正在进行的存根,并将其返回。 然后在OngoingStubbing实例上调用thenReturn方法。

简短的回答是,在幕后,Mockito使用某种全局variables/存储来保存方法存根构build步骤的信息(调用method(),when(),thenReturn()),这样最终可以build立一个关于什么是什么参数时应该返回的地图。

我发现这篇文章非常有帮助: 说明如何基于代理的模拟框架工作http://blog.rseiler.at/2014/06/explanation-how-proxy-based-mock.html )。 作者实现了一个演示Mocking框架,我发现了一个非常好的资源,供那些想要弄清楚这些Mocking框架如何工作的人使用。

在我看来,这是反模式的典型用法。 通常情况下,我们在实施方法时应该避免“副作用”,这意味着方法应该接受input并做一些计算并返回结果 – 除此之外没有其他变化。 但是Mockito只是故意违反了这个规则。 它的方法除了返回结果之外还存储一堆信息:Mockito.anyString(),mockInstance.method(),when(),然后返回时,它们都有特殊的“副作用”。 这也是为什么这个框架乍看之下看起来像是一个魔法 – 我们通常不写这样的代码。 然而,在嘲讽框架的情况下,这种反模式devise是一个伟大的devise,因为它导致非常简单的API。