Mockito 也能 Mock final 类和 final 方法了
以实际 Java 项目中的单元测试 Mock 框架基本是 Mockito 2 了,因为它有一个十分流畅的 API。Mockito 2也为 JUnit 5 配上了 MockitoExtension, 所以 JUnit 5 下使用 Mockito 2 的关节也打通了。但在我们享受 Mockito 2 便利的同时,与 JMockit 相比局限性就很明显,因为 Mockito 2 是通过创建匿名子类来进行 Mock 的,所以任何子类对父类无法突破的方面它都存在。譬如,final 类, final 方法, 私有方法, 静态方法, 构造函数都是无法通过子类型进行重写的。所以除非特别需要,在 Mockito 2 无法胜任时都求助于 JMockit,JMockit 借助于 javaagent 取得了 JVM 的高控制权才得已为所欲为。
当 Mockito 来到了 2.1.0 版本,它也觉得不能对以上所有的限制置若罔闻, 首先带给我们的突破是它也可以 Mock final 类和 final 方法,虽然仍处于孵化器中,但毕竟是应用在单元测试中,能用就很不错了,只要以后不被拿走就行。这是官方对它的介绍 Mock the unmockable: opt-in mocking of final classes/methods
下面我亲自操作一遍,并给出更全方位的测试样例
如果上面的待测试类没有 final 类和 final 方法,上面三个测试用例都能成功通过。然而基于待测试类存在 final 修饰符的事实,以上测试用例将全部失败,它们的错误信息将分别是
但是当再加上一个待测试类
和测试用例
四个测试用例一起运行结果又不一样了
此时单独运行
该文件的内容是
有了
全绿,说明 Mockito 能对 final 类和 final 方法进行 Mock 了。这已经是 Mockito 的一大进步,先别对 private, static 有所奢欲。



基于
对于接口和抽象类的 Mock 行为始终没变,总是必须创建它们的子类型。
为什么是
既然前面猜测
有三个实现可选,
有了一个基本的推测后,顺道看一下 PowerMock 带来了什么 MockMaker 实现,在
[版权声明]
本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。
当 Mockito 来到了 2.1.0 版本,它也觉得不能对以上所有的限制置若罔闻, 首先带给我们的突破是它也可以 Mock final 类和 final 方法,虽然仍处于孵化器中,但毕竟是应用在单元测试中,能用就很不错了,只要以后不被拿走就行。这是官方对它的介绍 Mock the unmockable: opt-in mocking of final classes/methods
下面我亲自操作一遍,并给出更全方位的测试样例
待测试类(final 类与 final 方法的组合)
1class NonFinalClassWithFinalMethod {
2 final String finalMethod() {
3 return "something";
4 }
5}
6
7final class FinalClassWitnNonFinalMethod {
8 String nonFinalMethod() {
9 return "somthing";
10 }
11}
12
13final class FinalClassWithFinalMethod {
14 final String finalMethod() {
15 return "something";
16 }
17}对 final 修饰符视而不见的 Mock 测试
1package cc.unmi;
2
3import org.hamcrest.CoreMatchers;
4import org.junit.Test;
5import org.junit.runner.RunWith;
6import org.mockito.Mockito;
7import org.mockito.junit.MockitoJUnitRunner;
8
9import static org.junit.Assert.assertThat;
10import static org.mockito.Mockito.when;
11
12@RunWith(MockitoJUnitRunner.class)
13public class FinalClassTest {
14
15 @Test
16 public void testNonFinalClassWithFinalMethod() {
17 NonFinalClassWithFinalMethod testMe = Mockito.mock(NonFinalClassWithFinalMethod.class);
18 when(testMe.finalMethod()).thenReturn("hello");
19 assertThat(testMe.finalMethod(), CoreMatchers.equalTo("hello"));
20 }
21
22 @Test
23 public void testFinalClassWithNonFinalMethod() {
24 FinalClassWitnNonFinalMethod testMe = Mockito.mock(FinalClassWitnNonFinalMethod.class);
25 when(testMe.nonFinalMethod()).thenReturn("hello");
26 assertThat(testMe.nonFinalMethod(), CoreMatchers.equalTo("hello"));
27 }
28
29 @Test
30 public void testFinalClassWithFinalMethod() {
31 FinalClassWithFinalMethod testMe = Mockito.mock(FinalClassWithFinalMethod.class);
32 when(testMe.finalMethod()).thenReturn("hello");
33 assertThat(testMe.finalMethod(), CoreMatchers.equalTo("hello"));
34 }
35}如果上面的待测试类没有 final 类和 final 方法,上面三个测试用例都能成功通过。然而基于待测试类存在 final 修饰符的事实,以上测试用例将全部失败,它们的错误信息将分别是
testNonFinalClassWithFinalMethod(): org.mockito.exceptions.misusing.MissingMethodInvocationException:就是说正常情况下 final class, final method 等是不受 Mockito 待见的。
when() requires an argument which has to be 'a method call on a mock'.
For example:
when(mock.getArticles()).thenReturn(articles); Also, this error might show up because:
1. you stub either of: final/private/equals()/hashCode() methods.
Those methods *cannot* be stubbed/verified.
Mocking methods declared on non-public parent classes is not supported.
2. inside when() you don't call method on mock but on some other object. testFinalClassWithNonFinalMethod(): org.mockito.exceptions.base.MockitoException:
Cannot mock/spy class cc.unmi.FinalClassWitnNonFinalMethod
Mockito cannot mock/spy because :
- final class testFinalClassWithFinalMethod: org.mockito.exceptions.base.MockitoException:
Cannot mock/spy class cc.unmi.FinalClassWitnNonFinalMethod
Mockito cannot mock/spy because :
- final class
但是当再加上一个待测试类1class NonFinalClassWithNonFinalMethod {
2 String nonFinalMethod() {
3 return "something";
4 }
5}和测试用例
1 @Test
2 public void testNonFinalClassWithNonFinalMethod() {
3 NonFinalClassWithNonFinalMethod testMe = Mockito.mock(NonFinalClassWithNonFinalMethod.class);
4 when(testMe.nonFinalMethod()).thenReturn("hello");
5 assertThat(testMe.nonFinalMethod(), CoreMatchers.equalTo("hello"));
6 }四个测试用例一起运行结果又不一样了
此时单独运行 testNonFinalClassWithFinalMethod 还是提示与之前一样的错误org.mockito.exceptions.misusing.MissingMethodInvocationException:
when() requires an argument which has to be 'a method call on a mock'.
For example:
when(mock.getArticles()).thenReturn(articles); Also, this error might show up because:
1. you stub either of: final/private/equals()/hashCode() methods.
Those methods *cannot* be stubbed/verified.
Mocking methods declared on non-public parent classes is not supported.
2. inside when() you don't call method on mock but on some other object.
添加配置文件达成对 final 类 final 方法的 Mock
那么 Mockito 声称能 Mock final 类和 final 方法的角决之道在哪里呢?一个额外的配置文件,对于 Maven 标准布局的项目,创建这个文件src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker如果是其他项目的话,应该只需保证在 classpath 下有文件
mockito-extensions/org.mockito.plugins.MockMaker该文件的内容是
mock-maker-inline对,它就像是 SPI 模式的类似
META-INF/services/org.junit.platform.engine.TestEngine 文件。有了
src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker 文件,及内容 mock-maker-inline 后,再次运行上面的测试用例
全绿,说明 Mockito 能对 final 类和 final 方法进行 Mock 了。这已经是 Mockito 的一大进步,先别对 private, static 有所奢欲。MockMaker 让 Mockito 发生了什么
我们可以通过以下几张图来了解在src/test/resources/mockio-extensions/org.mockito.plugins.MockMaker (mock-maker-inline) 前后发生了什么前前,考查 Mock 的对象

Mock 非 final 类创建子类
后后,再次考查 Mock 的对象

终于 final 类也可以 Mock 了, 不能生成子类,像是直接生成它的实例,也无可非

可是对于非 final 类型也不再生成子类了,和 mock final 类一样的行为了
基于
Mockito.mock(clazz) 直接创建实例这一事实,原理上我们可以使用 Mockito 来 Mock 构造函数的。前前后后,针对接口和抽象类
为抽象类创建子类型
为接口也是创建子类型
对于接口和抽象类的 Mock 行为始终没变,总是必须创建它们的子类型。
为什么是 mock-maker-inline
既然前面猜测 src/resources/mockito-extensions/org.mockito.plugins.MockMaker 像是 SPI 模式,那么它大约就真的是了,的确是。其中可以写 mock-maker-inline, 必定也可以写上别的什么,自然要查找下 MockMaker 接口有哪些实现
有三个实现可选,mock-maker-inline 毫无疑问对应的就是 InlineByteBuddyMockMaker, 默认的实现方式也很容易想像得出来就是 SubclassByteBuddyMockMaker, 猜测配置为 mock-maker-subclass, 经验证不对。如果要显式的指定采用 SubclassByteBuddyMockMaker, 那么 src/resource/mockto-extensions/org.mockito.plugins.MockMaker 文件的内容是org.mockito.internal.creation.bytebuddy.SubclassByteBuddyMockMaker而要用
ByteBuddyMockMaker 的话,该文件内容是org.mockito.internal.creation.bytebuddy.ByteBuddyMockMakerMockito 目前提供的所有三个 MockMaker 实现,只有
InlineByteBuddyMockMaker 能用来 Mock final 类和 final 方法,mock-maker-inline 也可以配置为完整的类名org.mockito.internal.creation.bytebuddy.InlineByteBuddyMockMaker细看
org.mockito.internal.creation.bytebuddy.InlineByteBuddyMockMaker 类能发现它其中使用到了 JDK 的 Instrumentation API 和 ByteBuddyAgent 在生成实例时对方法实现进行了替换。PowerMockito(PowerMock) 是什么来头
根据 Mockito 预留下来的MockMaker 的实现,实际上给了第三方实现进行扩展对静态或私有方法的 Mock 了,这也就是让 Mockito 让 PowerMock 有可乘之机。因此,如果要 Mock 静态或私有方法的话,除 JMockit 外还有另一选择,那就是 PowerMock,而且还能继续使用 Mockito 的 API。有了一个基本的推测后,顺道看一下 PowerMock 带来了什么 MockMaker 实现,在
pom.xml 加入当前最新的 powermock-api-mockito 模块1<dependency>
2 <groupId>org.powermock</groupId>
3 <artifactId>powermock-api-mockito2</artifactId>
4 <version>1.7.4</version>
5 <scope>test</scope>
6</dependency>powermock-api-mockito2 在Maven 中央仓库正式版当前为 1.7.4, 所带的 Mockito 还是 2.8.9, 当前 Mockito 版本是 2.18.3, 落后也并不大。在 Maven 中央仓库还有 2.0.0-beta.5 的 powermock-api-mockito2, 所带的 Mockito 2 是 2.10.0 版本。powermock-api-mockito2 为 Mockito 2 提供了 MockMaker 的一个第三方实现是org.powermock.api.mockito.mockmaker.PowerMockMaker也正是它完善了 Mockito 2 对静态方法,私有方法,以及构造函数的 Mock 的,所以说本质上
powermock-api-mockito2 就是 Mockito 2 的一个扩展。
永久链接 https://yanbin.blog/mockito-mock-final-class-final-method/, 来自 隔叶黄莺 Yanbin's Blog[版权声明]
本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。