目录

  • 背景
  • 方法
    • 正常方法
    • 阅读源码时可以使用的方法
  • 什么情况下可以不创建派生类
  • 原理
  • 小结

背景

前面两篇提到,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相关推荐

  1. Mockito 也能 Mock final 类和 final 方法了

    以实际 Java 项目中的单元测试 Mock 框架基本是 Mockito 了,因为它有一个十分流畅的 API.Mockito 也为 JUnit 5 配上了 MockitoExtension, 所以 J ...

  2. java final 类_在Java中,final修饰的类有什么特点

    展开全部 关于Java中的32313133353236313431303231363533e4b893e5b19e31333264663736final(2010-09-09 14:19:48)转载▼ ...

  3. 《Java 核心技术卷1 第10版》学习笔记------ 组织继承:final类和方法【编译器优化:内联( inlining );】

    使用 final 关键字阻止继承 有时候,可能希望阻止人们利用某个类定义子类.不允许扩展的类被称为 final 类.如果在定义类的时候使用了 final 修饰符就表明这个类是 final 类. 例如, ...

  4. final 实例域+final类+final方法(阻止继承)

    [0]README 0.1)本文描述+源代码均 转自 core java volume 1, 旨在理清 "final 实例域": 0.2) 最后还增加了 阻止继承 的内容,涵盖了f ...

  5. 使用final类的作用是什么?

    问题:使用final类的作用是什么? 我在看一本关于Java的书,它里面说你可以定义一个类为final.我搞不明白有什么地方会被用到这样. 我是一个编程萌新.我想知道程序员在他们的程序里面都是怎么用f ...

  6. [转载] Java中的final变量、final方法和final类

    参考链接: Java中的final数组 | Final arrays 1.final变量 final关键字可用于变量声明,一旦该变量被设定,就不可以再改变该变量的值.通常,由final定义的变量为常量 ...

  7. 11.4 final类

    1.概述 a.定义为final的类不允许其他人对这个类进行任何改动,也不允许任何类继承. b.如果将这个列设置为final形式,则类中的所有方法都被隐式设置为final形式,但是final类中的成员变 ...

  8. Java final类详解

    1.什么是final类? Java中有一些类,如String,Math等,就是final类的典型例子. 虽然在Java编程中并不经常使用final类和final方法,但它们有着与众不同的特点,即fin ...

  9. Java final类

    final 具有"不可改变"的含义,他可以修饰非抽象类.非抽象成员方法和变量. 1.用final修饰的类不能被继承,没有子类. 2.用final修饰的方法不能被子类的方法重写或隐藏 ...

最新文章

  1. 钉钉、支付宝合种树,2-4天领证,限量9个名额
  2. mac连接群晖的服务器会自动断开_酷玩家庭数码-mac苹果笔记本电脑如何访问群晖NAS文件?...
  3. boost::hana::iterate用法的测试程序
  4. dojo中的dojo/dom-class
  5. SAP CRM和C4C数据同步的两种方式概述:SAP PI和HCI
  6. C语言中“指针”和“指针变量”的区别是什么
  7. python车牌识别逆光怎么办代码_这摄像头除了能逆光识别车牌,还会跟人打招呼?...
  8. Python快速构建神经网络
  9. LVS详解(四)——LVS安装与配置命令
  10. 使用PhoneNumberValidator判断用户输入的电话格式,并用PhoneFormatter对电话号码格式化。...
  11. ps安装插件提示“无法加载扩展,因为它未正确签署”怎么办?PS插件未经签署解决方法
  12. wps如何删除空白页?wps删除空白页的方法
  13. android两边是椭圆的按钮,自定义Button形状(圆形、椭圆)
  14. 解决鼠标右键失效(响应极慢)
  15. BURP APP HTTPS抓包xposed+justtrustme工具篇
  16. 浅谈泰勒公式与麦克劳林公式
  17. 1378:最短路径(shopth)——Floyd
  18. wince调节屏幕亮度
  19. 并发容器J.U.C -- AQS组件(一)
  20. python中的mat的操作

热门文章

  1. 【转】学习如何学习的算法:简述元学习研究方向现状
  2. 信息安全(一)之椭圆曲线方程
  3. Word基本操作之论文格式调整
  4. IDEA打jar包,如何跳过Test测试
  5. 汇编学习pushl, popl
  6. 2. Antlr 的快速使用
  7. 钉钉为什么被并入阿里云?
  8. linux zynq ps dma,Zynq PS DMA控制器应用笔记
  9. 【华为上机真题】消消乐游戏
  10. How to Enhance Our Sense of Happiness? 如何提升我们的幸福感