mockito when()调用如何工作?

时间:2013-01-21 14:00:20

标签: unit-testing mocking mockito

鉴于以下Mockito声明:

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

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

2 个答案:

答案 0 :(得分:102)

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

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

首先,当你使用mock类的Mockito方法模拟一个类时,这基本上就是这样:

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

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

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

    1. mock.method()
    2. when(<result of step 1>)
    3. <result of step 2>.thenReturn
    4. 理解正在发生的事情的关键是调用mock上的方法时会发生什么:方法拦截器传递有关方法调用的信息,并委托给它的MockHandler个实例链,最终委托到MockHandlerImpl#handle。在MockHandlerImpl#handle期间,模拟处理程序创建OngoingStubbingImpl的实例并将其传递给共享的MockingProgress实例。

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

答案 1 :(得分:24)

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

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

在我看来,它是反模式的典型用法。通常我们应该避免副作用&#39;当我们实现一个方法时,意味着该方法应该接受输入并进行一些计算并返回结果 - 除此之外没有其他任何改变。但莫基托故意违反了这条规则。它的方法存储了一堆信息,除了返回结果:Mockito.anyString(),mockInstance.method(),when(),thenReturn,它们都有特殊的副作用&#39;。这也是为什么框架在第一眼就看起来像一个魔术 - 我们通常不会编写这样的代码。但是,在模拟框架的情况下,这种反模式设计是一个很棒的设计,因为它会导致非常简单的API。