美文网首页
Mockito之代码分析

Mockito之代码分析

作者: 骊骅 | 来源:发表于2018-04-18 23:54 被阅读166次

    文章分析基于mockito的2.8.9版本

    之前的文章介绍了Mockito的使用,现在来分析一下Mockito的实现原理。Mockito里面使用到的类图如下。

    image.png

    mock

    以如下代码为触发点

    public class Debug {
    
        @Test
        public void test() {
            List mock = mock(LinkedList.class);
            when(mock.get(0)).thenReturn("hello");
            mock.get(0);
            verify(mock).get(0);
        }
    }
    
    
    • Mockito
      Mockito里面有几个mock的方法重载,最终都会使用到下面这个签名的mock方法。classToMock是被mock的类或是接口。mockSettings 是本次mock的一些设置。接下来跳转到MockitoCore
    public static <T> T mock(Class<T> classToMock, MockSettings mockSettings) {
            return MOCKITO_CORE.mock(classToMock, mockSettings);
        }
    
    • MockitoCore
    public <T> T mock(Class<T> typeToMock, MockSettings settings) {
            if (!MockSettingsImpl.class.isInstance(settings)) {
                throw new IllegalArgumentException("Unexpected implementation of '" + settings.getClass().getCanonicalName() + "'\n" + "At the moment, you cannot provide your own implementations of that class.");
            }
            MockSettingsImpl impl = MockSettingsImpl.class.cast(settings);
            MockCreationSettings<T> creationSettings = impl.confirm(typeToMock);
            T mock = createMock(creationSettings);
            mockingProgress().mockingStarted(mock, creationSettings);
            return mock;
        }
    

    MockitoCore里面做了一些初始化,然后流转到MockUtil

    • MockUtil
    
    public static <T> T createMock(MockCreationSettings<T> settings) {
            MockHandler mockHandler =  createMockHandler(settings);
    
            T mock = mockMaker.createMock(settings, mockHandler);
    
            Object spiedInstance = settings.getSpiedInstance();
            if (spiedInstance != null) {
                new LenientCopyTool().copyToMock(spiedInstance, mock);
            }
    
            return mock;
        }
        
    

    这个方法里面先创建了MockHandler,通过查看具体代码可以看到里面实例化了 MockHandlerImpl,并通过InvocationNotifierHandler包装了一下。然后是调用mocker的createMock方法。

    • ByteBuddyMockMaker

    debug可以看出代码里面调用的是ByteBuddyMockMaker,该类的createMock方法又调用了SubclassByteBuddyMockMaker的createMock方法。接下来重点看SubclassByteBuddyMockMaker

    • SubclassByteBuddyMockMaker
    
    public <T> T createMock(MockCreationSettings<T> settings, MockHandler handler) {
            Class<? extends T> mockedProxyType = createMockType(settings);
    
            Instantiator instantiator = Plugins.getInstantiatorProvider().getInstantiator(settings);
            T mockInstance = null;
            mockInstance = instantiator.newInstance(mockedProxyType);
            MockAccess mockAccess = (MockAccess) mockInstance;
            mockAccess.setMockitoInterceptor(new MockMethodInterceptor(asInternalMockHandler(handler), settings));
    
            return ensureMockIsAssignableToMockedType(settings, mockInstance);
                
    

    这个方法做了两件事[1]创建代理类;[2]实例化该代理类

    [1]创建代理类

    通过代码可以看到,最终调用的是SubclassBytecodeGenerator里面的下面方法

    public <T> Class<? extends T> mockClass(MockFeatures<T> features) {
            DynamicType.Builder<T> builder =
                    byteBuddy.subclass(features.mockedType)
                             .name(nameFor(features.mockedType))
                             .ignoreAlso(isGroovyMethod())
                             .annotateType(features.mockedType.getAnnotations())
                             .implement(new ArrayList<Type>(features.interfaces))
                             .method(matcher)
                               .intercept(to(DispatcherDefaultingToRealMethod.class))
                               .transform(withModifiers(SynchronizationState.PLAIN))
                               .attribute(INCLUDING_RECEIVER)
                             .method(isHashCode())
                               .intercept(to(MockMethodInterceptor.ForHashCode.class))
                             .method(isEquals())
                               .intercept(to(MockMethodInterceptor.ForEquals.class))
                             .serialVersionUid(42L)
                             .defineField("mockitoInterceptor", MockMethodInterceptor.class, PRIVATE)
                             .implement(MockAccess.class)
                               .intercept(FieldAccessor.ofBeanProperty());
            if (features.serializableMode == SerializableMode.ACROSS_CLASSLOADERS) {
                builder = builder.implement(CrossClassLoaderSerializableMock.class)
                                 .intercept(to(MockMethodInterceptor.ForWriteReplace.class));
            }
            if (readReplace != null) {
                builder = builder.defineMethod("readObject", void.class, Visibility.PRIVATE)
                        .withParameters(ObjectInputStream.class)
                        .throwing(ClassNotFoundException.class, IOException.class)
                        .intercept(readReplace);
            }
            return builder.make()
                          .load(new MultipleParentClassLoader.Builder()
                                  .append(features.mockedType)
                                  .append(features.interfaces)
                                  .append(currentThread().getContextClassLoader())
                                  .append(MockAccess.class, DispatcherDefaultingToRealMethod.class)
                                  .append(MockMethodInterceptor.class,
                                          MockMethodInterceptor.ForHashCode.class,
                                          MockMethodInterceptor.ForEquals.class).build(MockMethodInterceptor.class.getClassLoader()),
                                  loader.getStrategy(features.mockedType))
                          .getLoaded();
        }
        
    
    

    这段长代码其实主要是动态生成了一个代理类,这个代理类继承了目标类同时实现了MockAccess接口,正因为实现了这个接口,在可以调用这两行代码:

    
    MockAccess mockAccess = (MockAccess) mockInstance;
            mockAccess.setMockitoInterceptor(new MockMethodInterceptor(asInternalMockHandler(handler), settings));
            
    

    这里面的代理类是通过字节码的方式实现的,使用的是ByteBuddy这个框架,它并不需要编译器的帮助,而是直接生成class,然后使用ClassLoader来进行加载。

    至此,mockedProxyType这个代理类就创建好了。接下来看看代理类是如何实例化的。

    [2]实例化该代理类

    
    Instantiator instantiator = Plugins.getInstantiatorProvider().getInstantiator(settings);
    T mockInstance = null;
    mockInstance = instantiator.newInstance(mockedProxyType);
    
    

    Instantiator也是一个接口,它有两个实现,一是ObjenesisInstantiator,另外一个是ConstructorInstantiator, 默认情况下,都是在使用 ObjenesisInstantiator。看一下ObjenesisInstantiator的newInstance方法。

    
    public <T> T newInstance(Class<T> cls) {
            return objenesis.newInstance(cls);
        }
        
    

    objenesis是一个框架,可以根据出入的class,输出一个对象。github地址:https://github.com/easymock/objenesis

    我们的mock对象已经被生成出来,下面的代码是通过MockAccess接口设置MockMethodInterceptor,那么MockMethodInterceptor是如何被是用的呢?

    
    MockAccess mockAccess = (MockAccess) mockInstance; 
    
    mockAccess.setMockitoInterceptor(new MockMethodInterceptor(asInternalMockHandler(handler), settings));
    
    

    [3]重点代码

    回到上面mockClass的过程可以看到有下面这两行代码

    .method(matcher)
    .intercept(to(DispatcherDefaultingToRealMethod.class))
    
    

    因为构造函数传进来的matcher 就是any()。所以实际是

    .method(any())
    .intercept(to(DispatcherDefaultingToRealMethod.class))
    
    

    所以任何方法都会被org.mockito.internal.creation.bytebuddy.MockMethodInterceptor.DispatcherDefaultingToRealMethod这个类拦截。这个类是 MockMethodInterceptor里面的一个静态类,MockMethodInterceptor正是mockAccess set的类,DispatcherDefaultingToRealMethod的方法interceptSuperCallable最后会调用到MockMethodInterceptor里面的这个方法

    Object doIntercept(Object mock,
                           Method invokedMethod,
                           Object[] arguments,
                           InterceptedInvocation.SuperMethod superMethod,
                           Location location) throws Throwable {
            return handler.handle(new InterceptedInvocation(
                mock,
                createMockitoMethod(invokedMethod),
                arguments,
                superMethod,
                location,
                SequenceNumber.next()
            ));
        }
    

    可以看到handler.handle被调用。

    when

    • Mockito
    
     public static <T> OngoingStubbing<T> when(T methodCall) {
            return MOCKITO_CORE.when(methodCall);
        }
    
    • MockitoCore
    public <T> OngoingStubbing<T> when(T methodCall) {
            MockingProgress mockingProgress = mockingProgress();
            mockingProgress.stubbingStarted();
            @SuppressWarnings("unchecked")
            OngoingStubbing<T> stubbing = (OngoingStubbing<T>) mockingProgress.pullOngoingStubbing();
            if (stubbing == null) {
                mockingProgress.reset();
                throw missingMethodInvocation();
            }
            return stubbing;
        }
    

    为了搞清楚mockingProgressOngoingStubbing,需要回到 MockHandlerImpl的handle方法

    • MockHandlerImpl
    
    public Object handle(Invocation invocation) throws Throwable {
            if (invocationContainerImpl.hasAnswersForStubbing()) {
                // stubbing voids with doThrow() or doAnswer() style
                InvocationMatcher invocationMatcher = matchersBinder.bindMatchers(
                        mockingProgress().getArgumentMatcherStorage(),
                        invocation
                );
                invocationContainerImpl.setMethodForStubbing(invocationMatcher);
                return null;
            }
            VerificationMode verificationMode = mockingProgress().pullVerificationMode();
    
            InvocationMatcher invocationMatcher = matchersBinder.bindMatchers(
                    mockingProgress().getArgumentMatcherStorage(),
                    invocation
            );
    
            mockingProgress().validateState();
    
            // if verificationMode is not null then someone is doing verify()
            if (verificationMode != null) {
                // We need to check if verification was started on the correct mock
                // - see VerifyingWithAnExtraCallToADifferentMockTest (bug 138)
                if (((MockAwareVerificationMode) verificationMode).getMock() == invocation.getMock()) {
                    VerificationDataImpl data = createVerificationData(invocationContainerImpl, invocationMatcher);
                    verificationMode.verify(data);
                    return null;
                } else {
                    // this means there is an invocation on a different mock. Re-adding verification mode
                    // - see VerifyingWithAnExtraCallToADifferentMockTest (bug 138)
                    mockingProgress().verificationStarted(verificationMode);
                }
            }
    
            // prepare invocation for stubbing
            invocationContainerImpl.setInvocationForPotentialStubbing(invocationMatcher);
            OngoingStubbingImpl<T> ongoingStubbing = new OngoingStubbingImpl<T>(invocationContainerImpl);
            mockingProgress().reportOngoingStubbing(ongoingStubbing);
    
            // look for existing answer for this invocation
            StubbedInvocationMatcher stubbedInvocation = invocationContainerImpl.findAnswerFor(invocation);
            notifyStubbedAnswerLookup(invocation, stubbedInvocation);
    
            if (stubbedInvocation != null) {
                stubbedInvocation.captureArgumentsFrom(invocation);
                return stubbedInvocation.answer(invocation);
            } else {
                Object ret = mockSettings.getDefaultAnswer().answer(invocation);
                DefaultAnswerValidator.validateReturnValueFor(invocation, ret);
    
                // redo setting invocation for potential stubbing in case of partial
                // mocks / spies.
                // Without it, the real method inside 'when' might have delegated
                // to other self method and overwrite the intended stubbed method
                // with a different one. The reset is required to avoid runtime exception that validates return type with stubbed method signature.
                invocationContainerImpl.resetInvocationForPotentialStubbing(invocationMatcher);
                return ret;
            }
        }
        
    

    其中这三行代码与stub有关

    
         // prepare invocation for stubbing
            invocationContainerImpl.setInvocationForPotentialStubbing(invocationMatcher);
            OngoingStubbingImpl<T> ongoingStubbing = new OngoingStubbingImpl<T>(invocationContainerImpl);
            mockingProgress().reportOngoingStubbing(ongoingStubbing);
    

    因为任何方法的调用 都会先调用到handle这个方法,when调用的基本形式是when(mock.doSome()), 此时,当mock.doSome()时即会触发上面的语句。OngoingStubbingImpl表示正在对一个方法打桩的包装,invocationContainerImpl 相当于一个mock对象的管家,记录着mock对象方法的调用。后面我们还会再见到它。mockingProgress则可以理解为一个和线程相关的记录 器,用于存放每个线程正要准备做的一些事情,它的内部包含了几个report* 和 pull* 这样的函数,如上所看到,mockingProgress记录着ongoingStubbing对象
    再回过头来看MOCKITO_CORE 里的when方法就会清晰许多,它会取出刚刚存放在mockingProgress中的ongoingStubbing对象。OngoingStubbing<T> stubbing = mockingProgress.pullOngoingStubbing();

    thenReturn、thenThrow则是OngoingStubbing里的方法,这些方法最终都会调到如下方法:

    
    public OngoingStubbing<T> thenAnswer(Answer<?> answer) {
            if(!invocationContainerImpl.hasInvocationForPotentialStubbing()) {
                throw incorrectUseOfApi();
            }
    
            invocationContainerImpl.addAnswer(answer);
            return new ConsecutiveStubbing<T>(invocationContainerImpl);
        }
    

    invocationContainerImpl,它会帮我们保管这个answer,待以后调用该方法时返回正确的值,与之对应的代码是handle方法 中如下这句代码:

    StubbedInvocationMatcher stubbedInvocation = invocationContainerImpl.findAnswerFor(invocation);
    
    

    当stubbedInvocation不为空时,就会调用anwser方法来回去之前设定的值:

    stubbedInvocation.answer(invocation)
    
    

    至此 对于mock(doSomeMethod).thenReturn(some)的逻辑比较清楚了。

    verify

    • MockitoCore
    public <T> T verify(T mock, VerificationMode mode) {
            if (mock == null) {
                throw nullPassedToVerify();
            }
            if (!isMock(mock)) {
                throw notAMockPassedToVerify(mock.getClass());
            }
            MockingProgress mockingProgress = mockingProgress();
            VerificationMode actualMode = mockingProgress.maybeVerifyLazily(mode);
            mockingProgress.verificationStarted(new MockAwareVerificationMode(mock, actualMode, mockingProgress.verificationListeners()));
            return mock;
        }
    

    可见verify只是做了一些校验和准备的工作,最后直接返回了mock对象。因为通常是verify(mock).get(0)的形式,verify之后直接就调用了方法,所以还是会调用org.mockito.internal.handler.MockHandlerImpl#handle方法,这个方法里面的如下代码正式对应verify的

    // if verificationMode is not null then someone is doing verify()
            if (verificationMode != null) {
                // We need to check if verification was started on the correct mock
                // - see VerifyingWithAnExtraCallToADifferentMockTest (bug 138)
                if (((MockAwareVerificationMode) verificationMode).getMock() == invocation.getMock()) {
                    VerificationDataImpl data = createVerificationData(invocationContainerImpl, invocationMatcher);
                    verificationMode.verify(data);
                    return null;
                } else {
                    // this means there is an invocation on a different mock. Re-adding verification mode
                    // - see VerifyingWithAnExtraCallToADifferentMockTest (bug 138)
                    mockingProgress().verificationStarted(verificationMode);
                }
            }
    

    参考文档

    https://toutiao.io/posts/b0h8dz/preview

    相关文章

      网友评论

          本文标题:Mockito之代码分析

          本文链接:https://www.haomeiwen.com/subject/hxkikftx.html