常用的 Mock Toolkit 有 EasyMock、JMockit、Mockito、PowerMock 等,而本文主要介绍 Mockito 的简单使用,并对其实现原理进行分析。

1. 工作原理

Mockito 的使用方法如下,具体使用方法参考 Unit tests with Mockito - Tutorial[1]

  1. @Test
  2. public void testCallable() throws Exception {
  3. Callable mock = mock(Callable.class);
  4. when(mock.call()).thenReturn("first");
  5. Assert.assertEquals("first", mock.call());
  6. verify(mock, times(1)).call();
  7. }

1.1 代理阶段

下面我们从 Mockito.mock(Callable.class) 生成代理类开始进行分析。Mockito 使用 Byte Buddy[1] 框架生成动态代理类,因此我们需要先将代理类进行反编译才能看到源码。这里使用阿里开源 Java 应用诊断工具 Arthas[2] 反编译代理类,结果如下:

  1. /**
  2. * Arthas 反编译步骤:
  3. * 1. 启动 Arthas
  4. * java -jar arthas-boot.jar
  5. *
  6. * 2. 输入编号选择进程
  7. * Arthas 启动后,会打印 Java 应用进程列表,如下:
  8. * [1]: 5376
  9. * [2]: 7232 com.intellij.rt.execution.junit.JUnitStarter
  10. * [3]: 4692 org.gradle.launcher.daemon.bootstrap.GradleDaemon
  11. * [4]: 13000 org.jetbrains.jps.cmdline.Launcher
  12. * 这里输入编号 2,让 Arthas 关联到测试类的 Java 进程上
  13. *
  14. * 3. 使用 sc 命令搜索这个Callable接口对应的类名
  15. * $ sc org.mockito.codegen.Callable*
  16. * org.mockito.codegen.Callable$MockitoMock$1568092145
  17. * org.mockito.codegen.Callable$MockitoMock$1568092145$auxiliary$0leHbnY4
  18. * org.mockito.codegen.Callable$MockitoMock$1568092145$auxiliary$ce7uzpM8
  19. *
  20. * 4. 使用 jad 命令反编译 org.mockito.codegen.Callable$MockitoMock$1568092145
  21. * $ jad org.mockito.codegen.Callable$MockitoMock$1568092145
  22. *
  23. * 更多使用方法请参考 Arthas 官方文档:
  24. * https://alibaba.github.io/arthas/quick-start.html
  25. */
  26. package org.mockito.codegen;
  27. import java.lang.reflect.Method;
  28. import java.util.concurrent.Callable;
  29. import org.mockito.internal.creation.bytebuddy.MockAccess;
  30. import org.mockito.internal.creation.bytebuddy.MockMethodInterceptor;
  31. @FunctionalInterface
  32. public class Callable$MockitoMock$1568092145 implements Callable, MockAccess {
  33. private static final long serialVersionUID = 42L;
  34. private MockMethodInterceptor mockitoInterceptor;
  35. private static final /* synthetic */ Method cachedValue$PGk05LGb$cge4dj0;
  36. public Object call() throws Exception {
  37. return MockMethodInterceptor.DispatcherDefaultingToRealMethod.interceptAbstract(this, this.mockitoInterceptor, null, cachedValue$PGk05LGb$cge4dj0, new Object[0]);
  38. }
  39. @Override
  40. public MockMethodInterceptor getMockitoInterceptor() {
  41. return this.mockitoInterceptor;
  42. }
  43. @Override
  44. public void setMockitoInterceptor(MockMethodInterceptor mockMethodInterceptor) {
  45. this.mockitoInterceptor = mockMethodInterceptor;
  46. }
  47. static {
  48. cachedValue$PGk05LGb$cge4dj0 = Callable.class.getMethod("call", new Class[0]);
  49. }
  50. }

说明:如上,代理类的逻辑比较简单。Byte Buddy 生成的代理 Callable$MockitoMock$1568092145 的核心逻辑都委托给了 MockMethodInterceptor 处理。Mockito-1.x 版本中是采用 cglib 动态代理,2.x 版本改用了 bytebuddy。关于代理类的逻辑就说这么多,继续向下分析。

  1. public class MockMethodInterceptor implements Serializable {
  2. final MockHandler handler; // (核心类)
  3. private final MockCreationSettings mockCreationSettings;
  4. private final ByteBuddyCrossClassLoaderSerializationSupport serializationSupport;
  5. public MockMethodInterceptor(MockHandler handler, MockCreationSettings mockCreationSettings) {
  6. this.handler = handler;
  7. this.mockCreationSettings = mockCreationSettings;
  8. serializationSupport = new ByteBuddyCrossClassLoaderSerializationSupport();
  9. }
  10. Object doIntercept(Object mock, Method invokedMethod, Object[] arguments,
  11. RealMethod realMethod) throws Throwable {
  12. return doIntercept(mock, invokedMethod, arguments, realMethod, new LocationImpl());
  13. }
  14. Object doIntercept(Object mock, Method invokedMethod, Object[] arguments,
  15. RealMethod realMethod, Location location) throws Throwable {
  16. return handler.handle(createInvocation(mock, invokedMethod, arguments, realMethod, mockCreationSettings, location));
  17. }
  18. public static class DispatcherDefaultingToRealMethod {
  19. @RuntimeType
  20. public static Object interceptAbstract(@This Object mock,
  21. @FieldValue("mockitoInterceptor") MockMethodInterceptor interceptor,
  22. @StubValue Object stubValue, @Origin Method invokedMethod,
  23. @AllArguments Object[] arguments) throws Throwable {
  24. if (interceptor == null) {
  25. return stubValue;
  26. }
  27. return interceptor.doIntercept(mock, invokedMethod, arguments, RealMethod.IsIllegal.INSTANCE);
  28. }
  29. }
  30. }

说明:MockMethodInterceptor 拦截器最主要的功能包括组装执行参数和执行mock方法两个功能。核心的逻辑都委托给了 MockHandler。

  • 组装执行参数。DefaultInvocationFactory.createInvocation 创建执行参数 Invocation,它包含了执行对象(mock),执行的方法(invokedMethod) ,参数(arguments)。
  • 执行mock方法。最终 mock 方法执行都委托给了 MockHandler.handle(Invocation)

    1.2 执行阶段

    这部分的核心是 MockHandler。

1.2.1 整体流程

通过 Mockito.mock(Class) 创建的代理类后,首先需要注册方法对应的 stub,这些 stub 封装了该方法 Invocation 及其对应的行为 Answer,执行时则根据具体的方法来匹配这些 stub。

  • 注册过程:当调用 when(mock.call()).thenReturn("first") 时,会在 MockHandler 中添加该方法对应的 stub。
  • 调用过程:首先根据 Invocation 中的 “方法 invokedMethod” 和 “参数 arguments” 匹配对应的 stub。如果匹配上,则返回对应的 stub 执行,如果没有则返回默认值。

Mockito - 图1说明:MockHandler 是 Mockito 的核心处理模块,它持有 InvocationContainer 容器,可以根据方法 Invocation 注册和查找对应的 stub。当 Mockito.mock(Class) 生成代理类时,通过 MockHandlerFactory 工厂类创建 MockHandler,并设置到代理类中。

  1. public class MockHandlerFactory {
  2. public static <T> MockHandler<T> createMockHandler(MockCreationSettings<T> settings) {
  3. MockHandler<T> handler = new MockHandlerImpl<T>(settings);
  4. MockHandler<T> nullResultGuardian = new NullResultGuardian<T>(handler);
  5. return new InvocationNotifierHandler<T>(nullResultGuardian, settings);
  6. }
  7. }

说明:这几个 MockHandler 功能如下,重点是 MockHandlerImpl。

  • NullResultGuardian:用于返回默认值,具体见 Primitives.defaultValue
  • InvocationNotifierHandler:看类名就知道是在 mock 方法执行完成后触发事件监听。
  • MockHandlerImpl:(核心类)承担最核心的功能逻辑。MockHandlerImpl 中相关的核心类如下:
    • InvocationContainerImpl:其 stubbed 集合变量中存储所有自定义的 mock 方法。初始化阶段将 mock 方法包装成 StubbedInvocationMatcher 添加到集合中,执行时通过 Invocation 查找。
    • StubbedInvocationMatcher:具体的 stub,包含 Answer 和 Invocation 信息。它实现了 InvocationMatcher 和 Answer 接口,InvocationMatcher.matches(Invocation) 查看方法是否匹配,Answer.answer(InvocationOnMock) 则返回 mock 的执行结果。
    • Invocation:封装了执行的对象(mock),方法(invokedMethod) ,参数(arguments)。

      1.2.2 调用过程

      当调用代理类 mock.call() 方法时,最终会回调 MockHandlerImpl.handle(Invocation) 处理。该方法主要主要做了两件事,一是根据方法参数 invocation 查找 stub;二是执行 stub,如果没有则返回默认值。
  1. 查找 stub。调用 invocationContainer.findAnswerFor(invocation),根据方法的参数信息 invocation 从 invocationContainer 中获取注册的 stub。
  2. 执行 stub。如果已经注册了 stub,则直接调用 stubbing.answer(invocation) 返回结果。否则通过 mockSettings.getDefaultAnswer().answer(invocation) 返回默认值。
  3. 设置上下文环境。调用 invocationContainer.setInvocationForPotentialStubbing(invocationMatcher),将当前正在执行的方法参数信息 invocationMatcher,设置到 invocationContainer,这样 invocationContainer 就可以获取当前正在执行的方法信息 invocation。

    1. public class MockHandlerImpl<T> implements MockHandler<T> {
    2. InvocationContainerImpl invocationContainer;
    3. MatchersBinder matchersBinder = new MatchersBinder();
    4. private final MockCreationSettings<T> mockSettings;
    5. public MockHandlerImpl(MockCreationSettings<T> mockSettings) {
    6. this.mockSettings = mockSettings;
    7. this.matchersBinder = new MatchersBinder();
    8. this.invocationContainer = new InvocationContainerImpl(mockSettings);
    9. }
    10. public Object handle(Invocation invocation) throws Throwable {
    11. // 1. invocationMatcher用于匹配方法对应的stubbing
    12. InvocationMatcher invocationMatcher = matchersBinder.bindMatchers(
    13. mockingProgress().getArgumentMatcherStorage(), // 通常返回 null
    14. invocation // 参数信息
    15. );
    16. // 2. 设置当前正在执行的方法参数
    17. invocationContainer.setInvocationForPotentialStubbing(invocationMatcher);
    18. // 3. 根据invocation方法和参数,匹配stubbing。匹配成功则返回stub,否则返回默认值
    19. StubbedInvocationMatcher stubbing = invocationContainer.findAnswerFor(invocation);
    20. if (stubbing != null) {
    21. // 3.1 已经注册的stub
    22. stubbing.captureArgumentsFrom(invocation);
    23. return stubbing.answer(invocation);
    24. } else {
    25. // 3.2 返回默认值
    26. Object ret = mockSettings.getDefaultAnswer().answer(invocation);
    27. DefaultAnswerValidator.validateReturnValueFor(invocation, ret);
    28. invocationContainer.resetInvocationForPotentialStubbing(invocationMatcher);
    29. return ret;
    30. }
    31. }
    32. }

    说明:剩下还有一个问题,stub 是什么时候注册的。

    1.2.3 注册过程

    下面我们看 Mockito.``_when_``(``mock.call()).thenReturn(``**"**first**"**``) 发生了什么事?当我们 mock 一个对象的方法时,需要提供根据方法信息查找对应的 mock 方法的功能。

  • 首先,当调用 mock.call() 时,会设置 invocationContainer 的上下文环境 invocationMatcher,也就是方法信息 invocationForStubbing。
  • 其次,当调用 thenReturn(``**"**first**"**``) 时,将返回值包装成 answer。最后,将 “方法参数信息 invocation” 和 “执行逻辑 answer” 包封装到 StubbedInvocationMatcher,并添加到 stubbed 集合中。
  • 总之,StubbedInvocationMatcher 有了 “方法参数信息 invocationForStubbing” 和 “执行逻辑 answer”,执行时就可以通过 invocationForStubbing 匹配对应的 stub,并调用 answer 返回执行结果。

最后,我们来看一下 InvocationContainerImpl 代码,无非就是一个容器,提供了注册和查找 stub 的功能。

  1. // stubbed 集合保存所有注册的stub
  2. private final LinkedList<StubbedInvocationMatcher> stubbed = new LinkedList<StubbedInvocationMatcher>();
  3. // 当前正在执行方法的上下文-方法参数信息
  4. private MatchableInvocation invocationForStubbing;
  5. public StubbedInvocationMatcher addAnswer(Answer answer, boolean isConsecutive, Strictness stubbingStrictness) {
  6. Invocation invocation = invocationForStubbing.getInvocation();
  7. synchronized (stubbed) {
  8. Strictness effectiveStrictness = stubbingStrictness != null ? stubbingStrictness : this.mockStrictness;
  9. // (核心)invocationForStubbing是方法执行参数,而answer则是mock的执行逻辑
  10. stubbed.addFirst(new StubbedInvocationMatcher(answer, invocationForStubbing, effectiveStrictness));
  11. return stubbed.getFirst();
  12. }
  13. }
  14. public StubbedInvocationMatcher findAnswerFor(Invocation invocation) {
  15. synchronized (stubbed) {
  16. for (StubbedInvocationMatcher s : stubbed) {
  17. if (s.matches(invocation)) {
  18. s.markStubUsed(invocation);
  19. invocation.markStubbed(new StubInfoImpl(s));
  20. return s;
  21. }
  22. }
  23. }
  24. return null;
  25. }

2. 注解驱动

总结时刻

推荐阅读

  1. Mockito 官网》和《Mockito Github》。
  2. Unit tests with Mockito - Tutorial》:简单的使用手册。
  3. Mockito 也能 Mock final 类和 final 方法》:Mockito-2.x 之后使用 Byte-Buddy 进行动态代理。Byte-Buddy 整合 Java Instrumentation 工具类,支持修改已加载的类。Mockito 通过修改已经加载的类,来支持 final 或 static 方法的 Mock,具体详见 InlineBytecodeGenerator#mockClass。
  4. Mockito的使用及原理浅析》:Mockito-1.x 使用 CGLIG 进行动态代理。

每天用心记录一点点。内容也许不重要,但习惯很重要!