Mockito是一个Mock框架,让你用简单干净的API写漂亮的测试代码。Mockito通过ByteBuddy动态字节码生成技术生成mock类型(代理类),默认通过objenesis框架生成mock类型的对象实例,来实现目标方法的代理功能,最终实现我们自定义的行为。
生成的代理类是被代理类的子类,其重写了父类的方法,每个方法都利用ByteBuddy技术设置了MockMethodInterceptor拦截器,MockMethodInterceptor 的主要作用是将 mock 对象的拦截方法执行交给了 MockHandler 来处理,也就是最终mock实例对象的所有方法都会调用MockHandlerImpl的handle方法,这个方法需要重点关注(正文会详细说明)。
至于when()方法就是从ThreadLocal获取OngoingStubbingImpl,而thenReturn就是将我们指定的自定义返回值放入OngoingStubbingImpl中的InvocationContainerImpl属性中(InvocationContainerImpl对象引用在MockHandlerImpl中也有),当调用mock代理类的方法时,就会从InvocationContainerImpl取出指定的answer(就是你自定义返回值的包装对象),并将其中的值返回。
那么verify()方法的核心逻辑就是调用ArgumentMatcher的matches方法来判断mock对象方法入参是否符合我们的预期。
以上就是Mockito源码的主要核心逻辑简介,其他更细节的逻辑可以继续往下看正文讲解。该文章主要从四个部分进行讲解:mock代理对象创建、mock对象方法的打桩、mock对象方法的调用、mock对象方法的入参验证,本文所使用的单元测试代码示例如下所示。
import org.junit.Test;
import org.mockito.Mockito;
import java.util.List;
/**
* @description:
* @author: zhangyuxuan
* @date: 2021/10/4 下午7:44 周一
*/
public class MockitoCodeTest {
@Test
@SuppressWarnings("unchecked")
public void test1() {
List mock = Mockito.mock(List.class);
Mockito.when(mock.get(0)).thenReturn("123");
Mockito.doReturn("123").when(mock).get(0);
System.out.println(mock.get(0));
Mockito.verify(mock).get(0);
}
}
下面通过一个简单的示例来说明一下:
List mock = Mockito.mock(List.class);
上面的例子一般是我们mock的入口,调用Mockito的静态法方法
创建Mock对象的核心方法-createMock()的流程图如下:
下面看一下SubclassByteBuddyMockMaker中的createMock()方法的源代码,关键点都加了一些注释,这里就不再文字赘述了。
public T createMock(MockCreationSettings settings, MockHandler handler) {
//这里利用ByteBuddy动态生成mock类
Class extends T> mockedProxyType = createMockType(settings);
Instantiator instantiator = Plugins.getInstantiatorProvider().getInstantiator(settings);
T mockInstance = null;
try {
//根据类生成对象实例,使用Objenesis技术
mockInstance = instantiator.newInstance(mockedProxyType);
//生成的动态类实现了MockAccess,将MockMethodInterceptor对象实例放入代理类的属性中
MockAccess mockAccess = (MockAccess) mockInstance;
mockAccess.setMockitoInterceptor(new MockMethodInterceptor(handler, settings));
//从settings中获取mockToType类型,然后将mockInstance强转成mockToType类型
return ensureMockIsAssignableToMockedType(settings, mockInstance);
} catch (ClassCastException cce) {
throw new MockitoException(
join(
"ClassCastException occurred while creating the mockito mock :",
" class to mock : " + describeClass(settings.getTypeToMock()),
" created class : " + describeClass(mockedProxyType),
" proxy instance class : " + describeClass(mockInstance),
" instance creation by : " + instantiator.getClass().getSimpleName(),
"",
"You might experience classloading issues, please ask the mockito mailing-list.",
""),
cce);
} catch (org.mockito.creation.instance.InstantiationException e) {
throw new MockitoException(
"Unable to create mock instance of type '"
+ mockedProxyType.getSuperclass().getSimpleName()
+ "'",
e);
}
}
再展示下SubclassBytecodeGenerator的mockClass()方法中的关键代码,这个代码我有做改动,因为使用saveIn()方法将生成的动态代理类从内存中输出到指定的路径下,这样我们就可以查看动态生成的class文件了。
关于ByteBuddy技术的了解,可以参考以下文档:
ByteBuddy入门教程 - 知乎
bytebuddy简单入门_wanxiaoderen的博客-CSDN博客_bytebuddy
官方文档:Byte Buddy - runtime code generation for the Java virtual machine
//这里利用ByteBuddy字节码增强技术动态生成类
DynamicType.Builder builder =
byteBuddy
.subclass(features.mockedType)
.name(name)
.ignoreAlso(isGroovyMethod())
.annotateType(
features.stripAnnotations
? new Annotation[0]
: features.mockedType.getAnnotations())
.implement(new ArrayList(features.interfaces))
.method(matcher)
.intercept(dispatcher)
.transform(withModifiers(SynchronizationState.PLAIN))
.attribute(
features.stripAnnotations
? MethodAttributeAppender.NoOp.INSTANCE
: INCLUDING_RECEIVER)
.serialVersionUid(42L)
.defineField("mockitoInterceptor", MockMethodInterceptor.class, PRIVATE)
.implement(MockAccess.class)
.intercept(FieldAccessor.ofBeanProperty())
.method(isHashCode())
.intercept(hashCode)
.method(isEquals())
.intercept(equals);
DynamicType.Unloaded unloaded = builder.make();
try {
//这里使用saveIn方法将动态生成的类输出到指定目录下
unloaded.saveIn(new File("/Users/zhangyuxuan/IdeaProjects/coding/mockito/src/test/java/org"));
} catch (IOException e) {
e.printStackTrace();
}
return unloaded
.load(
classLoader,
loader.resolveStrategy(features.mockedType, classLoader, localMock))
.getLoaded();
下面展示下ByteBuddy技术生成的动态代理类,以方便大家理解。为了保证完整性,就不对下面代码精简了,感兴趣的同学可以对比查看一下,实际上只需要展示get方法即可。
package org.mockito.codegen;
import java.util.Collection;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Spliterator;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.function.UnaryOperator;
import java.util.stream.Stream;
import org.mockito.internal.creation.bytebuddy.MockAccess;
import org.mockito.internal.creation.bytebuddy.MockMethodInterceptor;
import org.mockito.internal.creation.bytebuddy.MockMethodInterceptor.DispatcherDefaultingToRealMethod;
import org.mockito.internal.creation.bytebuddy.MockMethodInterceptor.ForEquals;
import org.mockito.internal.creation.bytebuddy.MockMethodInterceptor.ForHashCode;
public class List$MockitoMock$890335103 implements List, MockAccess {
private static final long serialVersionUID = 42L;
private MockMethodInterceptor mockitoInterceptor;
public String toString() {
return (String)DispatcherDefaultingToRealMethod.interceptSuperCallable(this, this.mockitoInterceptor, cachedValue$MFvvCWXe$4cscpe1, new Object[0], new List$MockitoMock$890335103$auxiliary$FtDVeNQv(this));
}
protected Object clone() throws CloneNotSupportedException {
return DispatcherDefaultingToRealMethod.interceptSuperCallable(this, this.mockitoInterceptor, cachedValue$MFvvCWXe$7m9oaq0, new Object[0], new List$MockitoMock$890335103$auxiliary$s7kgjZIM(this));
}
public void forEach(Consumer var1) {
DispatcherDefaultingToRealMethod.interceptSuperCallable(this, this.mockitoInterceptor, cachedValue$MFvvCWXe$ascqpd0, new Object[]{var1}, new List$MockitoMock$890335103$auxiliary$3a9hfkuz(this, var1));
}
public Stream stream() {
return (Stream)DispatcherDefaultingToRealMethod.interceptSuperCallable(this, this.mockitoInterceptor, cachedValue$MFvvCWXe$4i8d8f1, new Object[0], new List$MockitoMock$890335103$auxiliary$n8Rih3Nh(this));
}
public boolean removeIf(Predicate var1) {
return (Boolean)DispatcherDefaultingToRealMethod.interceptSuperCallable(this, this.mockitoInterceptor, cachedValue$MFvvCWXe$aqin4c0, new Object[]{var1}, new List$MockitoMock$890335103$auxiliary$RoVqxHRT(this, var1));
}
public Stream parallelStream() {
return (Stream)DispatcherDefaultingToRealMethod.interceptSuperCallable(this, this.mockitoInterceptor, cachedValue$MFvvCWXe$bbf8080, new Object[0], new List$MockitoMock$890335103$auxiliary$dqMXE3c5(this));
}
public boolean add(Object var1) {
return (Boolean)DispatcherDefaultingToRealMethod.interceptAbstract(this, this.mockitoInterceptor, false, cachedValue$MFvvCWXe$sgg2351, new Object[]{var1});
}
public void add(int var1, Object var2) {
DispatcherDefaultingToRealMethod.interceptAbstract(this, this.mockitoInterceptor, (Object)null, cachedValue$MFvvCWXe$3us6oc3, new Object[]{var1, var2});
}
public boolean remove(Object var1) {
return (Boolean)DispatcherDefaultingToRealMethod.interceptAbstract(this, this.mockitoInterceptor, false, cachedValue$MFvvCWXe$v9lk1j0, new Object[]{var1});
}
public Object remove(int var1) {
return DispatcherDefaultingToRealMethod.interceptAbstract(this, this.mockitoInterceptor, (Object)null, cachedValue$MFvvCWXe$gs4cee0, new Object[]{var1});
}
public Object get(int var1) {
return DispatcherDefaultingToRealMethod.interceptAbstract(this, this.mockitoInterceptor, (Object)null, cachedValue$MFvvCWXe$2ff4l01, new Object[]{var1});
}
public boolean equals(Object var1) {
return ForEquals.doIdentityEquals(this, var1);
}
public int hashCode() {
return ForHashCode.doIdentityHashCode(this);
}
public int indexOf(Object var1) {
return (Integer)DispatcherDefaultingToRealMethod.interceptAbstract(this, this.mockitoInterceptor, 0, cachedValue$MFvvCWXe$tm4die2, new Object[]{var1});
}
public void clear() {
DispatcherDefaultingToRealMethod.interceptAbstract(this, this.mockitoInterceptor, (Object)null, cachedValue$MFvvCWXe$bp48n33, new Object[0]);
}
public boolean isEmpty() {
return (Boolean)DispatcherDefaultingToRealMethod.interceptAbstract(this, this.mockitoInterceptor, false, cachedValue$MFvvCWXe$dc8ju02, new Object[0]);
}
public int lastIndexOf(Object var1) {
return (Integer)DispatcherDefaultingToRealMethod.interceptAbstract(this, this.mockitoInterceptor, 0, cachedValue$MFvvCWXe$77a53p0, new Object[]{var1});
}
public boolean contains(Object var1) {
return (Boolean)DispatcherDefaultingToRealMethod.interceptAbstract(this, this.mockitoInterceptor, false, cachedValue$MFvvCWXe$q0m7l61, new Object[]{var1});
}
public void replaceAll(UnaryOperator var1) {
DispatcherDefaultingToRealMethod.interceptSuperCallable(this, this.mockitoInterceptor, cachedValue$MFvvCWXe$61en0h1, new Object[]{var1}, new List$MockitoMock$890335103$auxiliary$j6mAFEJB(this, var1));
}
public int size() {
return (Integer)DispatcherDefaultingToRealMethod.interceptAbstract(this, this.mockitoInterceptor, 0, cachedValue$MFvvCWXe$479u1c1, new Object[0]);
}
public List subList(int var1, int var2) {
return (List)DispatcherDefaultingToRealMethod.interceptAbstract(this, this.mockitoInterceptor, (Object)null, cachedValue$MFvvCWXe$idijvh3, new Object[]{var1, var2});
}
public Object[] toArray() {
return (Object[])DispatcherDefaultingToRealMethod.interceptAbstract(this, this.mockitoInterceptor, (Object)null, cachedValue$MFvvCWXe$pa58dn1, new Object[0]);
}
public Object[] toArray(Object[] var1) {
return (Object[])DispatcherDefaultingToRealMethod.interceptAbstract(this, this.mockitoInterceptor, (Object)null, cachedValue$MFvvCWXe$eqbjn92, new Object[]{var1});
}
public Iterator iterator() {
return (Iterator)DispatcherDefaultingToRealMethod.interceptAbstract(this, this.mockitoInterceptor, (Object)null, cachedValue$MFvvCWXe$itsld03, new Object[0]);
}
public Spliterator spliterator() {
return (Spliterator)DispatcherDefaultingToRealMethod.interceptSuperCallable(this, this.mockitoInterceptor, cachedValue$MFvvCWXe$ialm821, new Object[0], new List$MockitoMock$890335103$auxiliary$5pfANSxk(this));
}
public boolean addAll(Collection var1) {
return (Boolean)DispatcherDefaultingToRealMethod.interceptAbstract(this, this.mockitoInterceptor, false, cachedValue$MFvvCWXe$6epee82, new Object[]{var1});
}
public boolean addAll(int var1, Collection var2) {
return (Boolean)DispatcherDefaultingToRealMethod.interceptAbstract(this, this.mockitoInterceptor, false, cachedValue$MFvvCWXe$pqjhh82, new Object[]{var1, var2});
}
public Object set(int var1, Object var2) {
return DispatcherDefaultingToRealMethod.interceptAbstract(this, this.mockitoInterceptor, (Object)null, cachedValue$MFvvCWXe$5p89p02, new Object[]{var1, var2});
}
public boolean containsAll(Collection var1) {
return (Boolean)DispatcherDefaultingToRealMethod.interceptAbstract(this, this.mockitoInterceptor, false, cachedValue$MFvvCWXe$8o98bj1, new Object[]{var1});
}
public boolean removeAll(Collection var1) {
return (Boolean)DispatcherDefaultingToRealMethod.interceptAbstract(this, this.mockitoInterceptor, false, cachedValue$MFvvCWXe$3um2h43, new Object[]{var1});
}
public boolean retainAll(Collection var1) {
return (Boolean)DispatcherDefaultingToRealMethod.interceptAbstract(this, this.mockitoInterceptor, false, cachedValue$MFvvCWXe$2v2l442, new Object[]{var1});
}
public ListIterator listIterator(int var1) {
return (ListIterator)DispatcherDefaultingToRealMethod.interceptAbstract(this, this.mockitoInterceptor, (Object)null, cachedValue$MFvvCWXe$vv27a83, new Object[]{var1});
}
public ListIterator listIterator() {
return (ListIterator)DispatcherDefaultingToRealMethod.interceptAbstract(this, this.mockitoInterceptor, (Object)null, cachedValue$MFvvCWXe$ivs3a83, new Object[0]);
}
public void sort(Comparator var1) {
DispatcherDefaultingToRealMethod.interceptSuperCallable(this, this.mockitoInterceptor, cachedValue$MFvvCWXe$g7qoll1, new Object[]{var1}, new List$MockitoMock$890335103$auxiliary$xGvgLT35(this, var1));
}
public void setMockitoInterceptor(MockMethodInterceptor var1) {
this.mockitoInterceptor = var1;
}
public MockMethodInterceptor getMockitoInterceptor() {
return this.mockitoInterceptor;
}
public List$MockitoMock$890335103() {
}
static {
cachedValue$MFvvCWXe$gs4cee0 = List.class.getMethod("remove", Integer.TYPE);
cachedValue$MFvvCWXe$77a53p0 = List.class.getMethod("lastIndexOf", Object.class);
cachedValue$MFvvCWXe$479u1c1 = List.class.getMethod("size");
cachedValue$MFvvCWXe$pa58dn1 = List.class.getMethod("toArray");
cachedValue$MFvvCWXe$2v2l442 = List.class.getMethod("retainAll", Collection.class);
cachedValue$MFvvCWXe$itsld03 = List.class.getMethod("iterator");
cachedValue$MFvvCWXe$tm4die2 = List.class.getMethod("indexOf", Object.class);
cachedValue$MFvvCWXe$idijvh3 = List.class.getMethod("subList", Integer.TYPE, Integer.TYPE);
cachedValue$MFvvCWXe$ialm821 = List.class.getMethod("spliterator");
cachedValue$MFvvCWXe$vv27a83 = List.class.getMethod("listIterator", Integer.TYPE);
cachedValue$MFvvCWXe$4cscpe1 = Object.class.getMethod("toString");
cachedValue$MFvvCWXe$pqjhh82 = List.class.getMethod("addAll", Integer.TYPE, Collection.class);
cachedValue$MFvvCWXe$bp48n33 = List.class.getMethod("clear");
cachedValue$MFvvCWXe$8o98bj1 = List.class.getMethod("containsAll", Collection.class);
cachedValue$MFvvCWXe$3um2h43 = List.class.getMethod("removeAll", Collection.class);
cachedValue$MFvvCWXe$ascqpd0 = Iterable.class.getMethod("forEach", Consumer.class);
cachedValue$MFvvCWXe$5p89p02 = List.class.getMethod("set", Integer.TYPE, Object.class);
cachedValue$MFvvCWXe$g7qoll1 = List.class.getMethod("sort", Comparator.class);
cachedValue$MFvvCWXe$6epee82 = List.class.getMethod("addAll", Collection.class);
cachedValue$MFvvCWXe$ivs3a83 = List.class.getMethod("listIterator");
cachedValue$MFvvCWXe$bbf8080 = Collection.class.getMethod("parallelStream");
cachedValue$MFvvCWXe$3us6oc3 = List.class.getMethod("add", Integer.TYPE, Object.class);
cachedValue$MFvvCWXe$61en0h1 = List.class.getMethod("replaceAll", UnaryOperator.class);
cachedValue$MFvvCWXe$7m9oaq0 = Object.class.getDeclaredMethod("clone");
cachedValue$MFvvCWXe$4i8d8f1 = Collection.class.getMethod("stream");
cachedValue$MFvvCWXe$v9lk1j0 = List.class.getMethod("remove", Object.class);
cachedValue$MFvvCWXe$q0m7l61 = List.class.getMethod("contains", Object.class);
cachedValue$MFvvCWXe$eqbjn92 = List.class.getMethod("toArray", Object[].class);
cachedValue$MFvvCWXe$2ff4l01 = List.class.getMethod("get", Integer.TYPE);
cachedValue$MFvvCWXe$aqin4c0 = Collection.class.getMethod("removeIf", Predicate.class);
cachedValue$MFvvCWXe$sgg2351 = List.class.getMethod("add", Object.class);
cachedValue$MFvvCWXe$dc8ju02 = List.class.getMethod("isEmpty");
}
}
以上就是mockito框架动态生成mock代理类生成的过程,接下来我们继续后面的分享。
常用的打桩方式有两种,下面就分别进行源码分析;
第一种示例:
Mockito.when(mock.get(0)).thenReturn("123");
先说when方法中的mock.get(0),这里调用了mock代理类的get方法,会调用MockHandlerImpl中的handle方法,创建OngoingStubbingImpl并放入ThreadLocal中(实际上是放入MockingProgressImpl中,然后MockingProgressImpl再放入ThreadLocal中,我们还是挑重点说,无关紧要的细节省略掉,感兴趣的可以自己下载Mockito源码查看),而调用when方法时候会取出刚才放入的OngoingStubbingImpl对象实例并返回,注意这里是pull方式取出的,也就意味着出来以后ThreadLocal中就不包含OngoingStubbingImpl了,源码见下面。
public OngoingStubbing when(T methodCall) {//when方法返回OngoingStubbingImpl对象实例
MockingProgress mockingProgress = mockingProgress();//获取MockingProgressImpl对象实例
mockingProgress.stubbingStarted();//给MockingProgressImpl实例的属性stubbingInProgress赋值LocationImpl实例
@SuppressWarnings("unchecked")
OngoingStubbing stubbing = (OngoingStubbing) mockingProgress.pullOngoingStubbing();//从MockingProgressImpl实例中pull出OngoingStubbingImpl实例,MockingProgressImpl实例的ongoingStubbing属性会被置空
if (stubbing == null) {
mockingProgress.reset();
throw missingMethodInvocation();
}
return stubbing;
}
以上就是when方法的主要逻辑,接下来我们来看返回的OngoingStubbingImpl中的thenReturn方法,这个方法会将传入的参数包装成Answer类型(我们自定义的返回值封装到Answer对象的value属性中),然后放入OngoingStubbingImpl中的InvocationContainerImpl属性中,不过这里需要注意的是MockHandlerImpl中也存了InvocationContainerImpl属性,和OngoingStubbingImpl中的是同一个,thenReturn源码如下。
public OngoingStubbing thenAnswer(Answer> answer) {
if (!invocationContainer.hasInvocationForPotentialStubbing()) {
throw incorrectUseOfApi();
}
invocationContainer.addAnswer(answer, strictness);//向InvocationContainerImpl中添加answer
return new ConsecutiveStubbing(invocationContainer);//连续存根
}
第二种示例:
Mockito.doReturn("123").when(mock).get(0);
这里的doReturn实际是调用StubberImpl中的doReturnValues方法,将自定义指定的返回值封装成Answer放入StubberImpl的属性中保存并返回StubberImpl对象实例,StubberImpl中的when方法当然就会将保存的Answer放入InvocationContainerImpl中并返回mock对象实例,最后又回到MockHandlerImpl的handle方法了,这里会将我们指定Answer和InterceptedInvocation绑定,到此就完成了打桩工作,绑定逻辑代码如下。
if (invocationContainer.hasAnswersForStubbing()) {
// stubbing voids with doThrow() or doAnswer() style
InvocationMatcher invocationMatcher =
matchersBinder.bindMatchers(
mockingProgress().getArgumentMatcherStorage(), invocation);
invocationContainer.setMethodForStubbing(invocationMatcher);
return null;
}
前面mock对象和打桩都已经准备完成,接下来就要进行mock对象方法调用了,还是以简单示例代码来进行讲解。
System.out.println(mock.get(0));
前面我们已经说过,mock对象的方法都会调入到MockHandlerImpl中的handle方法,返回结果会从MockHandlerImpl的InvocationContainerImpl属性中取出,具体代码如下。
public Object handle(Invocation invocation) throws Throwable {
if (invocationContainer.hasAnswersForStubbing()) {
// stubbing voids with doThrow() or doAnswer() style
InvocationMatcher invocationMatcher =
matchersBinder.bindMatchers(
mockingProgress().getArgumentMatcherStorage(), invocation);
invocationContainer.setMethodForStubbing(invocationMatcher);
return null;
}
VerificationMode verificationMode = mockingProgress().pullVerificationMode();
//InvocationMatcher属性有Invocation和List>,通常情况下会将InterceptedInvocation中的arguments参数转成ArgumentMatcher,而InterceptedInvocation属性来自于代理类中传入
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 (MockUtil.areSameMocks(
((MockAwareVerificationMode) verificationMode).getMock(),
invocation.getMock())) {
VerificationDataImpl data =
new VerificationDataImpl(invocationContainer, 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
invocationContainer.setInvocationForPotentialStubbing(invocationMatcher);//将InvocationMatcher对象实例放入InvocationContainerImpl对象实例的属性MatchableInvocation中
OngoingStubbingImpl ongoingStubbing = new OngoingStubbingImpl(invocationContainer);//将InvocationContainerImpl对象实例放入OngoingStubbingImpl对象实例的属性InvocationContainerImpl中
mockingProgress().reportOngoingStubbing(ongoingStubbing);//将OngoingStubbingImpl对象实例放入MockingProgressImpl对象实例的属性OngoingStubbing中,后面再when调用中会取出来使用
// look for existing answer for this invocation
// 这里会从InvocationContainerImpl寻找已经存在的存根,第一次使用when(object.method)的时候,存根是空,第二次调用就会有存根了。
// 或者这样说更准确,就是调用OngoingStubbingImpl中的thenAnswer方法后,就会将设置的answer最终放入InvocationContainerImpl中的StubbedInvocationMatcher属性中。
// 而StubbedInvocationMatcher中的InterceptedInvocation也很重要,因为方法的比较和匹配需要用到,这个属性值是在。
StubbedInvocationMatcher stubbing = invocationContainer.findAnswerFor(invocation);
// TODO #793 - when completed, we should be able to get rid of the casting below
notifyStubbedAnswerLookup(
invocation,
stubbing,
invocationContainer.getStubbingsAscending(),
(CreationSettings) mockSettings);
if (stubbing != null) {
stubbing.captureArgumentsFrom(invocation);
try {
return stubbing.answer(invocation);
} finally {
// Needed so that we correctly isolate stubbings in some scenarios
// see MockitoStubbedCallInAnswerTest or issue #1279
mockingProgress().reportOngoingStubbing(ongoingStubbing);//在返回打桩指定的返回值以后,再把OngoingStubbingImpl放入MockingProgressImpl中
}
} else {//第一次调用会走这个分支,或者说没有进行打桩之前,都是走着分支
Object ret = mockSettings.getDefaultAnswer().answer(invocation);
DefaultAnswerValidator.validateReturnValueFor(invocation, ret);
// Mockito uses it to 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.
// This means we would be stubbing a wrong method.
// Typically this would led to runtime exception that validates return type with stubbed
// method signature.
invocationContainer.resetInvocationForPotentialStubbing(invocationMatcher);
return ret;
}
}
如上所说,我们就实现了mock代理类的方法返回我们自定义的返回值,至此核心代码的核心逻辑已经很清晰了。
我们除了对单元测试的返回结果进行验证,还需要对mock对象的方法调用的入参进行验证,看看入参是不是和我们预期是一致的,这就要使用verify方法进行验证了,下面还是用最简单的示例来讲解源码。
Mockito.verify(mock).get(0);
verify方法像mock和when方法一样,调用的也是MockitoCore中的同名方法,入参中VerificationMode类型参数如果不传,默认就会是times(1)方法返回的Times对象实例,调用过程中会将VerificationMode放入ThreadLocal中,其返回值通常就是传入的mock对象,具体细节见下面核心源码。
public T verify(T mock, VerificationMode mode) {
if (mock == null) {
throw nullPassedToVerify();
}
MockingDetails mockingDetails = mockingDetails(mock);
if (!mockingDetails.isMock()) {
throw notAMockPassedToVerify(mock.getClass());
}
assertNotStubOnlyMock(mock);
MockHandler handler = mockingDetails.getMockHandler();
mock =
(T)
VerificationStartedNotifier.notifyVerificationStarted(//通常情况下这返回的mock对象还是DefaultMockingDetails中的mock对象,也就是传入的mock对象
handler.getMockSettings().getVerificationStartedListeners(),
mockingDetails);
MockingProgress mockingProgress = mockingProgress();
VerificationMode actualMode = mockingProgress.maybeVerifyLazily(mode);
mockingProgress.verificationStarted(//将VerificationMode设置到MockingProgressImpl中的Localized属性
new MockAwareVerificationMode(
mock, actualMode, mockingProgress.verificationListeners()));
return mock;
}
接下来我们又要回到MockHandlerImpl的handle方法调用中了,上面已经展示过源代码,这里就根据讲解,截取片段进行说明。
invocationContainer.setInvocationForPotentialStubbing(invocationMatcher);//将InvocationMatcher对象实例放入InvocationContainerImpl对象实例的属性MatchableInvocation中;将每次调用的InterceptedInvocation放到DefaultRegisteredInvocations的LinkedList属性中,以做记录
这里会记录每次mock代理对象的方法调用的InterceptedInvocation,放入List中。
VerificationMode verificationMode = mockingProgress().pullVerificationMode();//这里pull出VerificationMode,在verify的时候会用到
//InvocationMatcher属性有Invocation和List>,通常情况下会将InterceptedInvocation中的arguments参数转成ArgumentMatcher,而InterceptedInvocation属性来自于代理类中传入
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 (MockUtil.areSameMocks(
((MockAwareVerificationMode) verificationMode).getMock(),
invocation.getMock())) {
VerificationDataImpl data =//获取验证数据
new VerificationDataImpl(invocationContainer, 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);
}
}
这里会取出在verify调用时候放入的VerificationMode,然后调用其verify方法,相关数据会通过VerificationDataImpl传入。
public void verify(VerificationData data) {
List invocations = data.getAllInvocations();
MatchableInvocation wanted = data.getTarget();
if (wantedCount > 0) {
checkMissingInvocation(data.getAllInvocations(), data.getTarget());
}
checkNumberOfInvocations(invocations, wanted, wantedCount);
}
checkMissingInvocation方法中会将刚才在handle方法中记录的InterceptedInvocation类型的list中的相同方法(会对比方法名和入参类型)和当前VerificationMode中的进行匹配(对于本文的例子来说就是对比方法名和入参类型以及入参值是否一致),如果没有发现匹配的就会抛出异常。
checkNumberOfInvocations方法会将InterceptedInvocation类型的list和Times中的wantedCount进行对比,不一致将抛出异常;
至此,一个最简单的Mockito单元测试示例的源代码就分析完了,接下来再对常用的verify写法简单分析一下,具体讲解看代码上面的注释。
//这里虽然传入的是0,但是也会在MockHandlerImpl的handle方法调用的时候,包装成ArgumentMatcher对象实例,ArgumentMatcher有很多子类
Mockito.verify(mock).get(0);
//这里一定不要用any()方法,因为any方法只适用于对象参数,而这里入参是int,如果你用了any(),那么一定会报空指针
Mockito.verify(mock).get(ArgumentMatchers.anyInt());
//这种写法和第一种比较相似,但是有个好处就是多个入参的时候,用起来很方便。
//举个例子:入参3个,前2个需要验证,第3个不需要验证,那么久就可以这样写method(ArgumentMatchers.eq("需要验证的值1"), ArgumentMatchers.eq("需要验证的值2"), any())
Mockito.verify(mock).get(ArgumentMatchers.eq(0));
//上面使用的eq、anyInt等都是框架帮我们封装好的,我们也可以进行自定义,那么久需要我们自己传入ArgumentMatcher接口实现类,并重写matches方法。
//在进行验证比较的时候,会分别判断mock对象是否相同、方法名称和入参类型是否一致、还有就是调用ArgumentMatcher实现类的matches方法判断入参值是否一致
Mockito.verify(mock).get(ArgumentMatchers.intThat(argument -> true));
Mockito.verify(mock).remove(ArgumentMatchers.argThat(argument -> true));
总的来看Mockito源码设计还是挺巧的,让使用就就像说话一样去写单元测试,符合常人的思维逻辑。其中也有黑科技ByteBuddy与Objenesis的强势加盟,以及多数框架使用的ThreadLocal。最重要的代码就是MockHandlerImpl的handle方法了,这个方法逻辑非常多,虽然代码都是出现在同一方法内,但他们并不是都在同一时空工作的,有些代码视为when服务的,有些视为verify服务的,还有为doReturn服务的。经过源码的分析,相信读者就更理解Mockito框架的实现原理了,正因为有了如此巧妙的实现,才支撑我们写出干净整洁的单元测试。
PS:后面也会计划对spy、AgumentCaptor、注解写法以及PowerMock进行源码分析,敬请期待!