您好!
欢迎来到京东云开发者社区
登录
首页
博文
课程
大赛
工具
用户中心
开源
首页
博文
课程
大赛
工具
开源
更多
用户中心
开发者社区
>
博文
>
Mockito源码浅析 — 核心逻辑
分享
打开微信扫码分享
点击前往QQ分享
点击前往微博分享
点击复制链接
Mockito源码浅析 — 核心逻辑
自猿其说Tech
2021-12-30
IP归属:未知
226880浏览
计算机编程
### 1 Mockito源码逻辑简介 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源码的实现方式,那大概的应该是这样的:把我们指定的方法调用和返回值绑定在一起,当我们在调用这个方法的时候,返回对应的返回值,而对于入参验证也是如此,只不过不是把返回值换成入参了,那么我们就带着这样的猜想去理解Mockito源码吧。 以上就是Mockito源码的主要核心逻辑简介,其他更细节的逻辑可以继续往下看正文讲解。该文章主要从四个部分进行讲解:mock代理对象创建、mock对象方法的打桩、mock对象方法的调用、mock对象方法的入参验证,本文所使用的单元测试代码示例如下所示。 ```java 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<String> 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); } } ``` ### 2 mock代理对象创建 下面通过一个简单的示例来说明一下: ```java List<String> mock = Mockito.mock(List.class); ``` 上面的例子一般是我们mock的入口,调用Mockito的静态法方法<T> T mock(Class<T> classToMock),Mockito中mock重载方法有好几个,不过最终都是调用的<T> T mock(Class<T> classToMock, MockSettings mockSettings)方法,具体调用关系和返回值可以参照下面的时序图。 ![](//img1.jcloudcs.com/developer.jdcloud.com/2d5ef29b-1f1f-4824-a288-27f7b3505aaf20211230141555.png) 创建Mock对象的核心方法-createMock()的流程图如下: ![](//img1.jcloudcs.com/developer.jdcloud.com/9bdee668-60ab-451a-a352-c2c46c56b07120211230141611.png) 下面看一下SubclassByteBuddyMockMaker中的createMock()方法的源代码,主要逻辑点已经在上面的流程图中标识出来了,关键点都加了一些注释,这里就不再文字赘述了。 ```java public <T> T createMock(MockCreationSettings<T> 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技术的了解,可以参考以下文档: - https://zhuanlan.zhihu.com/p/151843984 - https://blog.csdn.net/wanxiaoderen/article/details/106544773 - 官方文档:https://bytebuddy.net/#/tutorial ```java //这里利用ByteBuddy字节码增强技术动态生成类 DynamicType.Builder<T> builder = byteBuddy .subclass(features.mockedType) .name(name) .ignoreAlso(isGroovyMethod()) .annotateType( features.stripAnnotations ? new Annotation[0] : features.mockedType.getAnnotations()) .implement(new ArrayList<Type>(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<T> 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()方法即可。 我们看一下get()方法内部代码,DispatcherDefaultingToRealMethod.interceptAbstract(this, this.mockitoInterceptor, (Object)null, cachedValue$MFvvCWXe$2ff4l01, new Object[]{var1})的几个主要的参数分析下,第一个参数是mock代理对象,第二个参数是我们在createMock()方法中创建的MockMethodInterceptor对象实例,并放入了代理类实例的mockitoInterceptor属性中(也就是这里的this对象),最后一个参数是我们调用get()方法时传入的参数。 第二个参数this.mockitoInterceptor很关键,其中就包含MockHandlerImpl对象实例,这个实例中就包含handle()方法,所有的代理方法都会调用handle(),这里面包含了Mockito框架大部分的核心逻辑,这个方法会在后面进行详细介绍。 ```java 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代理类生成的过程。 ### 3 mock对象方法的打桩 常用的打桩方式有两种,下面分别进行源码分析; #### 第一种示例: ```java Mockito.when(mock.get(0)).thenReturn("123"); ``` 先说when方法中的mock.get(0),这里调用了mock代理类的get()方法,会调用MockHandlerImpl中的handle()方法,方法中会创建OngoingStubbingImpl实例并放入ThreadLocal中(实际上是放入MockingProgressImpl中,然后MockingProgressImpl再放入ThreadLocal中),而调用when()方法时候会取出刚才放入的OngoingStubbingImpl对象实例并返回,注意这里是pull方式取出的,也就意味着出来以后ThreadLocal中就不包含OngoingStubbingImpl了。 其实按照我们正常的编码习惯,上述代码是比较奇怪的,好像when()方法的入参依赖mock.get(0)的返回值,实际上when()方法入参并不依赖mock.get(0)的具体返回值,通过下面的源码也可以看出,mock.get(0)的具体返回值压根就没有使用(不过返回值的类型还是有用的,因为要传给泛型),Mockito这样设计是为了让编码更符合人类正常的表达逻辑(当……时候,那么就返回……)。 when()方法的源码如下: ```java public <T> OngoingStubbing<T> when(T methodCall) {//when方法返回OngoingStubbingImpl对象实例 MockingProgress mockingProgress = mockingProgress();//获取MockingProgressImpl对象实例 mockingProgress.stubbingStarted();//给MockingProgressImpl实例的属性stubbingInProgress赋值LocationImpl实例 @SuppressWarnings("unchecked") OngoingStubbing<T> stubbing = (OngoingStubbing<T>) 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中的是同一个对象引用,因为MockHandlerImpl中的handle()方法会使用这个对象,返回该InvocationContainerImpl对象中存放的对应的Answer对象的value属性值。 thenReturn()方法源码如下。 ```java public OngoingStubbing<T> thenAnswer(Answer<?> answer) { if (!invocationContainer.hasInvocationForPotentialStubbing()) { throw incorrectUseOfApi(); } invocationContainer.addAnswer(answer, strictness);//向InvocationContainerImpl中添加answer return new ConsecutiveStubbing<T>(invocationContainer);//连续存根 } ``` #### 第二种示例: ```java Mockito.doReturn("123").when(mock).get(0); ``` 这里的doReturn()方法实际是调用StubberImpl中的doReturnValues()方法,将自定义指定的返回值封装成Answer,并放入StubberImpl的属性中保存,然后返回StubberImpl对象实例。 接下来StubberImpl中的when()方法就会将保存的Answer放入InvocationContainerImpl中,并返回mock对象实例。 最后的get(0)又会进入MockHandlerImpl的handle()方法了,这里会将我们指定Answer和InterceptedInvocation绑定(注意此时if中的表达式结果为true,因为when()方法已经将doReturn()方法返回的Answer对象放入了invocationContainer中了),到此就完成了打桩工作,等到后面再次调用代理方法的时候,就会根据相应的InterceptedInvocation(本例中就是list.get(0))返回我们指定的返回值。 绑定逻辑代码如下。 ```java if (invocationContainer.hasAnswersForStubbing()) { // stubbing voids with doThrow() or doAnswer() style InvocationMatcher invocationMatcher = matchersBinder.bindMatchers( mockingProgress().getArgumentMatcherStorage(), invocation); invocationContainer.setMethodForStubbing(invocationMatcher); return null; } ``` ### 4 mock对象方法的调用 前面mock对象和打桩都已经准备完成,接下来就要进行mock对象方法调用了,还是以简单示例代码来进行讲解。 ```java System.out.println(mock.get(0)); ``` 前面我们已经说过,mock对象的方法都会调入到MockHandlerImpl中的handle()方法,返回结果会从MockHandlerImpl的InvocationContainerImpl属性中取出,这个handle()方法中包含了各种场景下的逻辑,虽然代码都是出现在同一方法内,但他们并不是都在同一时空工作的,有些代码是为when()服务的,有些是为verify()服务的,还有为doReturn()服务的。具体代码如下。 ```java 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<ArgumentMatcher<?>>,通常情况下会将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<T> ongoingStubbing = new OngoingStubbingImpl<T>(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代理类的方法返回我们自定义的返回值,至此核心代码的核心逻辑已经很清晰了。 ### 5 mock对象方法的入参验证 我们除了对单元测试的返回结果进行验证,还需要对mock对象的方法调用的入参进行验证,看看入参是不是和我们预期是一致的,这就要使用verify方法进行验证了,下面还是用最简单的示例来讲解源码。 ```java Mockito.verify(mock).get(0); ``` verify方法像mock()和when()方法一样,调用的也是MockitoCore中的同名方法,入参中VerificationMode类型参数如果不传,默认就会是times(1)方法返回的Times对象实例,调用过程中会将VerificationMode放入ThreadLocal中,其返回值通常就是传入的mock对象,具体细节见下面核心源码。 ```java public <T> 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<VerificationMode>属性new MockAwareVerificationMode( mock, actualMode, mockingProgress.verificationListeners())); return mock; } ``` 接下来我们又要回到MockHandlerImpl的handle()方法调用中了,上面已经展示过源代码,这里就根据需要,截取片段进行说明。 下面的代码会记录每次mock代理对象的方法调用的InterceptedInvocation,放入List中。 ```java //将InvocationMatcher对象实例放入InvocationContainerImpl对象实例的属性MatchableInvocation中;将每次调用的InterceptedInvocation放到DefaultRegisteredInvocations的LinkedList<Invocation>属性中,以做记录 invocationContainer.setInvocationForPotentialStubbing(invocationMatcher); ``` 下面的代码会取出在verify调用时候放入的VerificationMode,然后调用其verify()方法,相关数据会通过VerificationDataImpl传入。 ```java VerificationMode verificationMode = mockingProgress().pullVerificationMode();//这里pull出VerificationMode,在verify的时候会用到 //InvocationMatcher属性有Invocation和List<ArgumentMatcher<?>>,通常情况下会将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); } } ``` checkMissingInvocation方法中会将刚才在handle()方法中记录的InterceptedInvocation类型的list中的相同方法(会对比方法名和入参类型)和当前VerificationMode中的进行匹配(对于本文的例子来说就是对比方法名和入参类型以及入参值是否一致),如果没有发现匹配的就会抛出异常。 checkNumberOfInvocations方法会将InterceptedInvocation类型的list和Times中的wantedCount进行对比,不一致将抛出异常; ```java public void verify(VerificationData data) { List<Invocation> invocations = data.getAllInvocations(); MatchableInvocation wanted = data.getTarget(); if (wantedCount > 0) { checkMissingInvocation(data.getAllInvocations(), data.getTarget()); } checkNumberOfInvocations(invocations, wanted, wantedCount); } ``` 至此,一个最简单的Mockito单元测试示例的源代码就分析完了,接下来再对常用的verify写法简单分析一下,具体讲解看代码上面的注释。 ```java //这里虽然传入的是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)); ``` ### 6 总结 总的来看Mockito源码设计还是很巧妙的,让使用者就像说话一样去写单元测试,符合常人的思维逻辑。其中也有黑科技ByteBuddy与Objenesis的强势加盟,以及框架经常使用的ThreadLocal。最重要的代码就是MockHandlerImpl的handle()方法了,这个方法逻辑非常多,虽然代码都是出现在同一方法内,但他们并不是都在同一时空工作的,有些代码是为when()服务的,有些是为verify()服务的,还有为doReturn()服务的。经过源码的分析,相信读者就更理解Mockito框架的实现原理了,正因为有了如此巧妙的实现,才支撑我们写出干净整洁的单元测试。 ------------ ###### 自猿其说Tech-京东物流技术发展部 ###### 作者:张宇轩
原创文章,需联系作者,授权转载
上一篇:视频超分技术实践与应用
下一篇:Java微基准性能测试:数字转字符串方式哪家强?JMH来帮忙
相关文章
Taro小程序跨端开发入门实战
Flutter For Web实践
配运基础数据缓存瘦身实践
自猿其说Tech
文章数
426
阅读量
2149964
作者其他文章
01
深入JDK中的Optional
本文将从Optional所解决的问题开始,逐层解剖,由浅入深,文中会出现Optioanl方法之间的对比,实践,误用情况分析,优缺点等。与大家一起,对这项Java8中的新特性,进行理解和深入。
01
Taro小程序跨端开发入门实战
为了让小程序开发更简单,更高效,我们采用 Taro 作为首选框架,我们将使用 Taro 的实践经验整理了出来,主要内容围绕着什么是 Taro,为什么用 Taro,以及 Taro 如何使用(正确使用的姿势),还有 Taro 背后的一些设计思想来进行展开,让大家能够对 Taro 有个完整的认识。
01
Flutter For Web实践
Flutter For Web 已经发布一年多时间,它的发布意味着我们可以真正地使用一套代码、一套资源部署整个大前端系统(包括:iOS、Android、Web)。渠道研发组经过一段时间的探索,使用Flutter For Web技术开发了移动端可视化编程平台—Flutter乐高,在这里希望和大家分享下使用Flutter For Web实践过程和踩坑实践
01
配运基础数据缓存瘦身实践
在基础数据的常规能力当中,数据的存取是最基础也是最重要的能力,为了整体提高数据的读取能力,缓存技术在基础数据的场景中得到了广泛的使用,下面会重点展示一下配运组近期针对数据缓存做的瘦身实践。
自猿其说Tech
文章数
426
阅读量
2149964
作者其他文章
01
深入JDK中的Optional
01
Taro小程序跨端开发入门实战
01
Flutter For Web实践
01
配运基础数据缓存瘦身实践
添加企业微信
获取1V1专业服务
扫码关注
京东云开发者公众号