我怀疑
Mockito.spy()
返回参数对象副本的包装器,并且该包装器的类型是包装对象的生成子类型。那么间谍功能可能位于包装器的外部,就像通常在实现某些方面的代理对象的情况下一样。
不过,看起来好像不正确。包装器的类型与被包装对象的类型完全相同:
List<String> list = new ArrayList<>();
List<String> spyList = spy(list);
assertThat(spyList.getClass()).isSameAs(list.getClass());
那么间谍功能是在哪里实现的呢?如果
spyList
是 ArrayList
那么 Mockito 将如何收到有关对它的不同调用的通知?
Mockito 通过使用重新转换(“HotSwap”)、重写已加载类的字节码来实现这一点,因此 Mockito 甚至可以拦截
final
和系统类的行为。这是 5.0+ 版本中默认引入的新 MockitoMockMaker 的一个工件。这使得之前一些以子类为中心的 StackOverflow answers 有关 Mockito 内部结构的内容变得过时,尽管这些内容在 Android 和其他任何你不能指望仪器运行的地方仍然相关。
这个替代模拟生成器结合使用 Java 检测 API 和子类化,而不是创建一个新类来表示模拟。这样,就可以模拟最终类型和方法。了解该实现的最佳位置是
org.mockito.internal.creation.bytebuddy.InlineDelegateByteBuddyMockMaker,而不是主 Javadoc 第 39 项底部链接的 org.mockito.internal.creation.bytebuddy.InlineByteBuddyMockMaker FQCN。该类顶级 Javadoc 的最后一段,重点是我的:
请注意,内联模拟需要附加 Java 代理。 Mockito 将尝试附加 Java 代理 加载模拟生成器以创建内联模拟。这种运行时附加只有在使用 JVM 时才可能实现 是 JDK 的一部分或使用 Java 9 VM 时。然而,当在 Java 9 之前的非 JDK VM 上运行时,可以 使用浏览 ByteBuddy 的
-javaagent
手动添加 Byte Buddy Java 代理 jar启动 JVM 时的参数。此外,内联模拟生成器需要VM支持类重新转换 (也称为 HotSwap)。 所有主要 VM 发行版,例如 HotSpot (OpenJDK)、J9 (IBM/Websphere) 或 Zing (Azul) 支持这个功能。
Advice javadoc 了解上下文,以及 Mockito 的 MockMethodAdvice 和 MockMethodInterceptor 来了解 Mockito 如何分别在转换时和运行时实现这种魔力,也可能会有所帮助。
import static org.mockito.Mockito.spy;
import java.util.ArrayList;
import java.util.List;
import org.mockito.Mockito;
public class MyClass {
public static void main(String args[]) {
List<String> list = new ArrayList<>();
List<String> spyList = spy(list);
System.out.println(list.getClass() + " " + list.getClass().hashCode());
System.out.println(spyList.getClass() + " " + spyList.getClass().hashCode());
System.out.println(list == spyList);
System.out.println(list.getClass() == spyList.getClass());
System.out.println(Mockito.mockingDetails(list).isMock());
System.out.println(Mockito.mockingDetails(spyList).isMock());
}
}
与 JDoodle 一样,在 Mockito 4.11.0 (org.mockito:mockito-core:4.11.0) 中运行,此输出:
class java.util.ArrayList 1321659788
class org.mockito.codegen.ArrayList$MockitoMock$KpRNB6cF 257459516
false
false
false
true
但是在 Mockito 5.10.0 (org.mockito:mockito-core:5.10.0) 上,此输出:
class java.util.ArrayList 1321659788
class java.util.ArrayList 1321659788
false
true
false
true