Mockito 实现原理(3):如何对 final 类进行 mock
目录
- 背景
- 方法
- 正常方法
- 阅读源码时可以使用的方法
- 什么情况下可以不创建派生类
- 原理
- 小结
背景
前面两篇提到,Mockito 默认基于创建派生类(subclass)来实现 mock(包括 spy)。
那么问题来了,如果我的类标记为 final,明确禁止创建派生类,那不就没法 mock 了吗?
为了解决这个问题,Mockito 2 中引入了 InlineByteBuddyMockMaker
。和前面讨论过的默认的 SubclassByteBuddyMockMaker
相比,这个 InlineByteBuddyMockMaker
同样基于 Byte Buddy 这个提供 Java 字节码操作功能的第三方库,但会尽量不通过创建派生类来实现 mock。
(注:本文基于 Mockito 4.6.1 源码)
方法
正常方法
对 final 类进行 mock,需要用 InlineByteBuddyMockMaker
替换掉默认的 SubclassByteBuddyMockMaker
。
替换方法是通过创建一个配置文件。按照这篇教程,应该是在 src/test/resources/mockito-extensions
这个目录下,创建一个名为 org.mockito.plugins.MockMaker
的文件(这个名字其实就是 MockMaker 接口,我们其实就是在为这个接口指定一个实现,否则就会用默认的 SubclassByteBuddyMockMaker
实现了),然后在这个文件里写入:
mock-maker-inline
或者(下面这个是我在源码注释中看到的,其实就是我们要使用的实现类):
org.mockito.internal.creation.bytebuddy.InlineByteBuddyMockMaker
阅读源码时可以使用的方法
不幸的是,我用这个方法暂时还没有成功。但因为我是在研究 Mockito 的源码,所以我直接修改了源码中的这个文件:
// org/mockito/internal/configuration/plugins/DefaultMockitoPlugins.java
// 第 29 行开始
DEFAULT_PLUGINS.put(MockMaker.class.getName(),"org.mockito.internal.creation.bytebuddy.ByteBuddyMockMaker"); // 改之前
把最下面一行改成了:
// org/mockito/internal/configuration/plugins/DefaultMockitoPlugins.java
// 第 29 行开始
DEFAULT_PLUGINS.put(MockMaker.class.getName(),"org.mockito.internal.creation.bytebuddy.InlineByteBuddyMockMaker"); // 改之后
这样也实现了替换效果。
什么情况下可以不创建派生类
这个 InlineByteBuddyMockMaker
也不是万能的。在某些情况下,不创建派生类是行不通的,于是它本身会使用 SubclassByteBuddyMockMaker
作为兜底。
这些必须要创建派生类的情况包括:
- 被 mock 的类是抽象类;
- mock 时,设置了要额外实现的接口(这是 Mockito 的一个功能);
- mock 时,显性设置其支持序列化;
正常情况下我们就是直接 mock,不会设置什么要额外实现的接口或者序列化之类的。所以不用担心,我们的 final 类(或方法)通常来说就是可以 mock 的!
原理
InlineByteBuddyMockMaker
基于 Java Instrumentation API ,这是 Java 提供的一个可以像现有的已编译的类添加字节码的功能。
具体在源码中的体现:
// org/mockito/internal/creation/bytebuddy/InlineBytecodeGenerator.java
// 第 371 行
@Override
public byte[] transform(ClassLoader loader,String className,Class<?> classBeingRedefined,ProtectionDomain protectionDomain,byte[] classfileBuffer) {if (classBeingRedefined == null|| !mocked.contains(classBeingRedefined)&& !flatMocked.contains(classBeingRedefined)|| EXCLUDES.contains(classBeingRedefined)) {return null;} else {try {return byteBuddy.redefine(classBeingRedefined,// new ClassFileLocator.Compound(ClassFileLocator.Simple.of(classBeingRedefined.getName(), classfileBuffer)// ,ClassFileLocator.ForClassLoader.ofSystemLoader()// ))// Note: The VM erases parameter meta data from the provided class file// (bug). We just add this information manually..visit(new ParameterWritingVisitorWrapper(classBeingRedefined)).visit(mockTransformer).make().getBytes();} catch (Throwable throwable) {lastException = throwable;return null;}}
}
这个 transform
实现的是 java.lang.instrument.ClassFileTransformer
接口中的同名方法,总之就是对现有类的字节码进行修改,然后重新加载这个类。transform
就是用来“对现有类的字节码进行修改”的钩子方法。
不出意外,transform
也是借助了 Byte Buddy 实现了字节码修改,所以源码中没有体现出来很复杂的东西,真正复杂的地方都在 Byte Buddy 里了。
这里我还没完全看懂,反正无论如何,和前两篇讲到的 SubclassByteBuddyMockMaker
差不多,Mockito 会“植入”一个拦截器,这样你在调用 mock 对象的任何方法时都会走到这个拦截器里。这个拦截器会记录每次调用的信息,可以设置预期返回结果,等等。
小结
一个问题是,既然 InlineByteBuddyMockMaker
本身以 SubclassByteBuddyMockMaker
作为兜底,又增加了对 final 类/方法的支持,为什么不用它作为默认的 MockMaker 实现呢?还非得通过复杂的配置来切换。
原因大概是,InlineByteBuddyMockMaker
会对类本身进行修改,这不是一件好事,如果处理不当可能会带来意外问题。所以默认情况下,Mockito 是不鼓励用 InlineByteBuddyMockMaker
的,考虑到对 final 类/方法进行 mock 的需求不大,需要通过配置来实现也在情理之中了。
Mockito 实现原理(3):如何对 final 类进行 mock相关推荐
- Mockito 也能 Mock final 类和 final 方法了
以实际 Java 项目中的单元测试 Mock 框架基本是 Mockito 了,因为它有一个十分流畅的 API.Mockito 也为 JUnit 5 配上了 MockitoExtension, 所以 J ...
- java final 类_在Java中,final修饰的类有什么特点
展开全部 关于Java中的32313133353236313431303231363533e4b893e5b19e31333264663736final(2010-09-09 14:19:48)转载▼ ...
- 《Java 核心技术卷1 第10版》学习笔记------ 组织继承:final类和方法【编译器优化:内联( inlining );】
使用 final 关键字阻止继承 有时候,可能希望阻止人们利用某个类定义子类.不允许扩展的类被称为 final 类.如果在定义类的时候使用了 final 修饰符就表明这个类是 final 类. 例如, ...
- final 实例域+final类+final方法(阻止继承)
[0]README 0.1)本文描述+源代码均 转自 core java volume 1, 旨在理清 "final 实例域": 0.2) 最后还增加了 阻止继承 的内容,涵盖了f ...
- 使用final类的作用是什么?
问题:使用final类的作用是什么? 我在看一本关于Java的书,它里面说你可以定义一个类为final.我搞不明白有什么地方会被用到这样. 我是一个编程萌新.我想知道程序员在他们的程序里面都是怎么用f ...
- [转载] Java中的final变量、final方法和final类
参考链接: Java中的final数组 | Final arrays 1.final变量 final关键字可用于变量声明,一旦该变量被设定,就不可以再改变该变量的值.通常,由final定义的变量为常量 ...
- 11.4 final类
1.概述 a.定义为final的类不允许其他人对这个类进行任何改动,也不允许任何类继承. b.如果将这个列设置为final形式,则类中的所有方法都被隐式设置为final形式,但是final类中的成员变 ...
- Java final类详解
1.什么是final类? Java中有一些类,如String,Math等,就是final类的典型例子. 虽然在Java编程中并不经常使用final类和final方法,但它们有着与众不同的特点,即fin ...
- Java final类
final 具有"不可改变"的含义,他可以修饰非抽象类.非抽象成员方法和变量. 1.用final修饰的类不能被继承,没有子类. 2.用final修饰的方法不能被子类的方法重写或隐藏 ...
最新文章
- 钉钉、支付宝合种树,2-4天领证,限量9个名额
- mac连接群晖的服务器会自动断开_酷玩家庭数码-mac苹果笔记本电脑如何访问群晖NAS文件?...
- boost::hana::iterate用法的测试程序
- dojo中的dojo/dom-class
- SAP CRM和C4C数据同步的两种方式概述:SAP PI和HCI
- C语言中“指针”和“指针变量”的区别是什么
- python车牌识别逆光怎么办代码_这摄像头除了能逆光识别车牌,还会跟人打招呼?...
- Python快速构建神经网络
- LVS详解(四)——LVS安装与配置命令
- 使用PhoneNumberValidator判断用户输入的电话格式,并用PhoneFormatter对电话号码格式化。...
- ps安装插件提示“无法加载扩展,因为它未正确签署”怎么办?PS插件未经签署解决方法
- wps如何删除空白页?wps删除空白页的方法
- android两边是椭圆的按钮,自定义Button形状(圆形、椭圆)
- 解决鼠标右键失效(响应极慢)
- BURP APP HTTPS抓包xposed+justtrustme工具篇
- 浅谈泰勒公式与麦克劳林公式
- 1378:最短路径(shopth)——Floyd
- wince调节屏幕亮度
- 并发容器J.U.C -- AQS组件(一)
- python中的mat的操作