Mockito 源码解析

Mockito是Java平台上超火的Mock框架,因为其便捷的API,深受广大开发者喜爱。本文将从源码的角度,来分析Mockito的运行流程。

Mockito 简介

Mockito类相当于整个框架的门面,负责对外提供调用接口。常用的有如下几个:

  • mock

    1
    List list = Mockito.mock(List.class); 此时, list就是被Mockito所mock后生成的实例,Mockito会记住它所mock对象的所有调用,为后面的验证做准备。
    • 默认情况下,调用mock对象的带返回值的方法会返回默认的值,比如返回null、0值或者false等。
    • 允许多次代理mock对象的同一个方法,但具体的行为取决于该方法最近的一次代理行为。
    • mock对象的代理方法,允许多次调用,只有不覆盖它的代理行为,那么每次调用的执行相同的行为或者返回相同的值
    • 相同的方法和参数唯一确认一个代理。比如你可以分别代理get(int)方法在参数分别为0和1时的不同行为。
  • when

    1
    Mockito.when(list.size()).thenReturn(1);

    上述代码表示,当对list对象调用size()方法时,会返回1.这样我们就可以自定义mock对象的行为了。

    java 中的程序调用是以栈的形式实现的,对于 when 方法,list.size() 方法的调用对它是不可见的。when 能接收到的,只有 list.size() 的返回值。

  • verify

    1
    Mockito.verify(list).add(Matchers.anyObject());

    verify是负责验证的函数,接受的参数是一个被mock的对象,表示验证其是否执行了后面的方法。

Mockito 实现

Mock 使用了代理模式。我们操作的list,实际上是继承了List的代理类的实例,我们把它称为 proxy 对象。因此对于list 的所有操作,都会先经过 proxy 对象。

Mocktio 就是通过这种方式,拦截到了list 的所有操作。只要在调用list 的方法时,将该方法存放起来,然后在调用 thenReturn 为其设置返回值就可以了。

类图

img

几个主要的类已用红颜色标出,它们将会是Mockito的核心,也是下文主要介绍的对象。接下来,就深入Mockito的源码来一探究竟。

Mock

Mockito类中有几个mock函数的重载,最终都会调到mock(Class classToMock, MockSettings mockSettings),接受一个Class类型与一个MockSettings,class就是我们需要mock对象的类型,而mockSettings则记录着此次mock的一些信息。mock的行为实则转交个MockitoCore

1
MOCKITO_CORE.mock(classToMock, mockSettings)

MockitoCore中一是做了一下初始化工作,二是继续转交给MockUtil处理:

1
2
3
4
MockSettingsImpl impl = MockSettingsImpl.class.cast(settings);
MockCreationSettings<T> creationSettings = impl.confirm(typeToMock);
T mock = mockUtil.createMock(creationSettings);
mockingProgress.mockingStarted(mock, typeToMock);

MockUtil中则作了两件非常关键的事情:

1
2
MockHandler mockHandler = createMockHandler(settings);
T mock = mockMaker.createMock(settings, mockHandler);

一是创建了MockHandler对象,MockHandler是一个接口,通过createMockHandler函数我们可以清楚,MockHandler对象的实例是InvocationNotifierHandler类型,但它只是负责对外的包装,内部实际起作用的是MockHandlerImpl这个家伙,也是上文类图右下角的那位,这个类可谓承载了Mockito的主要逻辑,我们后面会详细的来说明。

接着会调用mockMaker来创建最终的实例,这个MockMaker也是一个接口,其实现为ByteBuddyMockMaker,我们追进去看一下createMock函数(省略异常处理代码):

1
2
3
4
5
6
7
8
9
public <T> T createMock(MockCreationSettings<T> settings, MockHandler handler){
Class<T> mockedProxyType = createProxyClass(mockWithFeaturesFrom(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);
}

首先函数内的第一行代码就比较引人注目:createProxyClass,要创建一个代理类, 但没有创建出目标类的实例。所以往下看,这个重担就交在了这三行代码上:

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

这个Instantiator也是一个接口,它有两个实现,一是ObjenesisInstantiator,另外一个是ConstructorInstantiator, 默认情况下,都是在使用ObjenesisInstantiator, 所以我们这里只查看ObjenesisInstantiator,并且ObjenesisInstantiator也是上述UML图中的一个标红的类,因为它承载生成对象的工作。看其newInstance方法:

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

这里调用了objenesis.newInstance,可以根据不同的平台选择不同的方法来new对象。总之一句话,只要输入一个class进去,它就会输出其一个实例对象.可以查看其Github:https://github.com/easymock/objenesis

如此,走到这里,我们的mock对象就已经被生成出来.

When

接下来看一下Mockito里的when方法:

1
2
3
public static <T> OngoingStubbing<T> when(T methodCall) {
return MOCKITO_CORE.when(methodCall);
}

同样是转交到了MOCKITO_CORE这里:

1
2
3
4
5
6
7
8
9
public <T> OngoingStubbing<T> when(T methodCall) {
mockingProgress.stubbingStarted();
OngoingStubbing<T> stubbing = mockingProgress.pullOngoingStubbing();
if (stubbing == null) {
mockingProgress.reset();
throw missingMethodInvocation();
}
return stubbing;
}

上文说过,mock对象所有的方法最终都会交由MockHandlerImpl的handle方法处理.

在handle中和when有关的几句代码是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public Object handle(Invocation invocation) throws Throwable {
...
// prepare invocation for stubbing
OngoingStubbingImpl<T> ongoingStubbing = new OngoingStubbingImpl<invocationContainerImpl);
mockingProgress.reportOngoingStubbing(ongoingStubbing)
// look for existing answer for this invocation
StubbedInvocationMatcher stubbedInvocation = invocationContainerImpl.findAnswerFor(invocation);
if (stubbedInvocation != null) {
stubbedInvocation.captureArgumentsFrom(invocation);
return stubbedInvocation.answer(invocation);
} else {
...
}
}

when调用的基本形式是when(mock.doSome()), 此时,当mock.doSome()时即会触发上面的语句,OngoingStubbingImpl表示正在对一个方法打桩的包装,invocationContainerImpl 相当于一个mock对象的管家,记录着mock对象方法的调用。后面我们还会再见到它。mockingProgress则可以理解为一个和线程相关的记录器,用于存放每个线程正要准备做的一些事情,它的内部包含了几个reportpull 这样的函数,如上所看到,mockingProgress记录着ongoingStubbing对象。

再回过头来看MOCKITO_CORE里的when方法就会清晰许多,它会取出刚刚存放在mockingProgress中的ongoingStubbing对象。OngoingStubbing<T> stubbing = mockingProgress.pullOngoingStubbing();

而我们熟知的thenReturnthenThrow则是OngoingStubbing里的方法,这些方法最终都会调到如下方法:

1
2
3
4
5
6
7
public OngoingStubbing<T> thenAnswer(Answer<?> answer) {
if(!invocationContainerImpl.hasInvocationForPotentialStubbing()) {
throw incorrectUseOfApi();
}
invocationContainerImpl.addAnswer(answer);
return new ConsecutiveStubbing<T>(invocationContainerImpl);
}

answer即是对thenReturnthenThrow之类的包装,当然我们也可以自己定义answer。我们又看到了invocationContainerImpl,它会帮我们保管这个answer,待以后调用该方法时返回正确的值,与之对应的代码是handle方法中如下这句代码:

1
StubbedInvocationMatcher stubbedInvocation = invocationContainerImpl.findAnswerFor(invocation);

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

1
stubbedInvocation.answer(invocation)

如此,对于when(mock.doSome()).thenReturn(obj)这样的用法的主要逻辑了。

参考:

Mockito 源码解析

动手编写 Mockito

Mockito 中文文档

Java动态代理与Cglib库

热评文章