()调用如何工作?

鉴于以下莫基托声明:

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

Mockito 是如何创建一个模拟代理的呢? 因为嘲弄.method ()语句会将返回值传递给 when ()?我想这会使用一些 CGLib 的东西,但是我想知道这是如何在技术上实现的。

90176 次浏览

简短的回答是,在您的示例中,mock.method()的结果将是一个类型适当的空值; 模拟器通过代理、方法拦截和 MockingProgress类的一个共享实例使用间接方法,以确定对模拟方法的调用是用于存根化还是重播现有存根化行为,而不是通过模拟方法的返回值传递关于存根化的信息。

在几分钟内对仿真代码进行一次小型分析,如下所示。注意,这是一个非常粗略的描述-有很多细节在这里发挥作用。我建议你自己检查一下 Github 上的消息来源

首先,当您使用 Mockito类的 mock方法模拟一个类时,实际上会发生以下情况:

  1. Mockito.mock将模拟设置作为参数传递给 org.mockito.internal.MockitoCore.mock。
  2. MockitoCore.mock委托给 org.mockito.internal.util.MockUtil.createMock
  3. MockUtil类使用 ClassPathLoader类获取 MockMaker的一个实例来创建模拟。默认情况下,使用 CgLibMockMaker类。
  4. CgLibMockMaker使用一个借自 JMock 的类 ClassImposterizer,该类处理创建模拟。使用的“仿真魔术”的关键部分是用于创建仿真的 MethodInterceptor: 仿真 MethodInterceptorFilter,以及一个 MockHandler 实例链,包括 MockHandlerImpl的一个实例。方法拦截器将调用传递给 MockHandlerImpl 实例,该实例实现在模拟上调用方法时应该应用的业务逻辑(即,搜索以查看是否已经记录了答案,确定调用是否表示一个新的存根,等等)。默认状态是,如果尚未为调用的方法注册存根,则返回一个适合类型的 空荡荡的值。

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

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

以下是此代码的执行顺序:

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

理解发生了什么的关键在于调用 mock 上的方法时会发生什么: 方法拦截器被传递有关方法调用的信息,并委托给它的 MockHandler实例链,最终委托给 MockHandlerImpl#handle。在 MockHandlerImpl#handle期间,模拟处理程序创建一个 OngoingStubbingImpl实例并将其传递给共享的 MockingProgress实例。

在调用 method()之后调用 when方法时,它将委托给 MockitoCore.whenMockitoCore.when调用同一类的 stub()方法。此方法从模拟的 method()调用写入的共享 MockingProgress实例中解压缩正在进行的存根,并返回它。然后对 OngoingStubbing实例调用 thenReturn方法。

简短的回答是,在幕后,Mockito 使用某种全局变量/存储来保存方法存根构建步骤的信息(例子中调用 method () ,when () ,then Return ()) ,这样最终它可以构建一个映射,显示在哪个参数上调用什么时应该返回什么。

我发现这篇文章很有帮助: 解释基于代理的模拟框架如何工作 (http://blog.rseiler.at/2014/06/explanation-how-proxy-based-mock.html)。 作者实现了一个演示 Mocking 框架,我为那些想要了解这些 Mocking 框架如何工作的人们找到了一个非常好的资源。

在我看来,这是反模式的典型用法。通常我们在实现一个方法时应该避免“副作用”,这意味着该方法应该接受输入并进行一些计算并返回结果——除此之外没有其他更改。但是 Mockito 是故意违反这条规则的。它的方法除了返回结果之外还存储大量的信息: Mockito.anyString ()、嘲 Instance.method ()、 when ()、 then Return,它们都有特殊的“副作用”。这也是为什么框架乍看起来像一个魔术-我们通常不会编写这样的代码。然而,在 Mocking 框架案例中,这种反模式设计是一个很好的设计,因为它带来了非常简单的 API。