Mockito之代码分析

2018-04-18  本文已影响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);
    }
}

public static <T> T mock(Class<T> classToMock, MockSettings mockSettings) {
        return MOCKITO_CORE.mock(classToMock, mockSettings);
    }
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


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方法。

debug可以看出代码里面调用的是ByteBuddyMockMaker,该类的createMock方法又调用了SubclassByteBuddyMockMaker的createMock方法。接下来重点看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


 public static <T> OngoingStubbing<T> when(T methodCall) {
        return MOCKITO_CORE.when(methodCall);
    }
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方法


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

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

上一篇下一篇

猜你喜欢

热点阅读