Mockito之代码分析
文章分析基于mockito的2.8.9版本
之前的文章介绍了Mockito的使用,现在来分析一下Mockito的实现原理。Mockito里面使用到的类图如下。
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;
}
为了搞清楚mockingProgress
和 OngoingStubbing
,需要回到 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);
}
}