查看原文
其他

微服务测试之Mockito的使用及实现原理

albenw SpringForAll社区 2021-05-27

点击上方☝SpringForAll社区 轻松关注!

及时获取有趣有料的技术文章

本文来源:http://r6d.cn/RfcL

概要

Mockito是一个强大的mock工具,本文将重点讲述Mockito的基本使用及注意事项,以及其实现mock的原理。

使用

应用场景

  • 开发完成之后,我们都需要经过测试才敢把代码发布到线上。一个很普遍的问题是,我们要测试的类可能会有很多对上游依赖,这些依赖包括类、对象、资源、数据等等,正常来说如果我们缺少这些依赖的话,我们将无法完成测试。这时候,我们一般的处理方法是mock,mock本地方法的返回,mock上游接口的返回等等,使这些方法返回假定的数据,好让我们流程可以继续走下去,完成测试。
  • Mock对于TDD来说也是很重要的一个功能。
  • 关于Mock在单元测试中的作用,可以看看Martin Fowler对它的讨论

简单例子

事例代码基于Mockito2.18.3版本。通常来说Mockito分为两大功能点,一是verify验证,一是stub打桩(我不知道怎么翻译好,跟立flag差不多意思) 先看看一个入门的例子

@Test
public void mockitoTest(){
    //生成一个mock对象
    List<String> mockedList = Mockito.mock(List.class);
    //打印mock对象的类名,看看mock对象为何物
    System.out.println(mockedList.getClass().getName());
    //操作mock对象
    mockedList.add("one");
    mockedList.get(0);
    mockedList.clear();
    //verify验证,mock对象是否发生过某些行为
    //如果验证不通过,Mockito会抛出异常
    Mockito.verify(mockedList).add("one");
    Mockito.verify(mockedList).get(0);
    //stub打桩,指定mock对象某些行为的返回,也就是我们常用的mock数据
    Mockito.when(mockedList.get(1)).thenReturn("13");
    //这里打印出“13”(但我们知道mockedList实际是没有下标为1的元素,这就是mock的功能)
    System.out.println(mockedList.get(1));
}

Mockito的使用很简单吧!相信经过上述代码的演示还有注释的说明,相信大家已经对Mockito有一个大概的认识。当然,上述代码只是演示了Mockito的一小部分功能,它还有更多、更强大的功能,我这里不将一一介绍。更多API请参考官方文档

注解使用

在我们的工程中,90%的情况都是基于Spring,使用JUnit作为单元测试框架,我们看看Mockito怎么与它们结合一起使用。看一个完整的JUnit单元测试类的例子(假设InjectMockService依赖MockServiceA和SpyServiceB)

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath*:applicationContext.xml" })
public class MockitoTest {

    @Autowired
    private CommonService commonService;

    @Mock
    private MockServiceA mockServiceA;

    @Autowired
    @Spy
    private SpyServiceB spyServiceB;

    @Autowired
 @InjectMocks
 private InjectMockService injectMockService; 

    @Before
    public void mockInit(){
     MockitoAnnotations.initMocks(this);
        MockServiceData();
        //...and so on
    }

    private void MockServiceData() {
        Object mockDataYouWantReturn = generateMockData();
        Mockito.when(mockServiceA.methodYouWantMock()).thenReturn(mockDataYouWantReturn);
    }

}

上述代码是我瞎编的,没有跑过。不过一般的使用场景也应该长的类似。对Mockito的注解说明一下:

  • @Mock,你需要mock的对象,其原理跟上述提到的Mockito.mock(List.class);差不多,即把注解标记的这个对象转换成Mockito的对象
  • @Spy,你需要进行部分mock的对象。这个Spy(翻译就是间谍啦)跟Mock相比,Mock是对对象的所有方法都进行mock处理的,即方法不会真正的执行,举之前的那个例子,mockedList.add("one");执行后不会把“one”加入到List中,但如果mockedList是一个Spy的话就可以,即把Mockito.mock(List.class);改为Mockito.spy(ArrayList.class);,那么add方法就会真正的执行,这里把List改为ArrayList是因为如果遇到abstract方法也不会执行的。Spy是有使用场景的,当我们需要mock一部分方法,而另外的方法需要正常执行时就需要用到Spy了,不然你就要mock全部用到的方法了
  • @InjectMocks,你需要注入@Mock对象的对象,即@InjectMocks这个对象依赖其他mock对象。这个有点像依赖注入,它就是用来解决这个问题的。举个例子说明,ServiceB依赖ServiceA,你是需要测试ServiceB中的某个方法,但是这个方法依赖到ServiceA了,ServiceA的返回你不可控,你需要mock它,这时就要把@Mock作用于ServiceA,@InjectMocks作用于ServiceB。

注意踩坑

这是我在使用Mockito遇到的一些坑,希望大家在实践中也注意一下。

@InjectMocks

@InjectMocks的作用是把@Mock的对象注入到属性中去,我们如果只写成(假设InjectMockService依赖MockServiceA)

   @InjectMocks
private InjectMockService injectMockService; 

   @Mock
   private MockServiceA mockServiceA;

这样的话,虽然InjectMockService会做初始化,而且MockServiceA会被注入到InjectMockService里,但是InjectMockService中其他的依赖对象会为null,因为你都没做其他初始化动作嘛。这时你就需要跟Spring的注入一起用,加上@Autowired注解,如下所示

   @InjectMocks
   @Autowired
private InjectMockService injectMockService;

从上Mockito的初始化得知,即上述代码的mockInit()方法,Mockito的初始化是在Spring的后面的,所以InjectMockService会被Spring初始化,然后再被Mockito修改依赖的Mock对象。

深层次对象

实际应用时,如果你想mock的对象在比较深的调用层次,那么做法可以是:获取依赖这个对象的对象(通过Spring的@Autowired),即mock对象的上一层对象,然后使用一些手段把mock对象替换调。例如使用ReflectionTestUtils.setField(upperObject, "fieldName", mockObject);把mock设置进去替换掉原来的对象。

Spy对象执行报错

在上面的例子介绍的用法中Mockito.when(mockedList.get(1)).thenReturn("13");但是如果mockedList是一个Spy对象的话,可能会有问题。这是因为@Spy对象会真正的实际执行该方法(@Mock则不会),但这是你要mock的方法,那么就有可能有问题。所以如果你不想方法实际执行,需要改变一下用法://不会调用stub的方法Mockito.doReturn(false).when(spyJack).go();

与Spring AOP

如果你Mock的对象被Spring AOP进行过处理,例如加了@Transactional或者自己做了切面等等,这时Spring会生成代理对象,这时要注意对你Mock对象有没有产生影响。

实现原理

源码基于Mockito2.18.3版本。上面介绍了Mockito的基本用法及注意事项。通过API的使用,大家一定很好奇Mockito究竟是怎么实现的。我们先尝试通过表象来推测其实现原理,然后再分析代码证实猜想。

表象猜测

如果你有多做测试,细心观察,就会发现通过Mockito生成的mock对象是一个“假”对象。对于文章开头的例子,我们可以发现

  • mockedList.add("one");不会实际往list增加一个“one”的元素(Spy则会),所以add之后你再get也是得不到结果的。即你无论怎么操作list都是徒劳的。
  • Mockito.verify(mockedList).add("one");这句很明显就是会校验list之前执行过的方法及入参。
  • Mockito.when(mockedList.get(1)).thenReturn("13");这句看上有点奇怪,怎么会用mockedList.get(1)作为入参呢?其实想想就知道不可能的,所以这里应该也是跟上面一样,只是get方法做了记录,然后再return值。

生成Mock对象

通过上面的猜测,我们很容易猜到Mockito用了代理生成了mock对象。我们直接跟踪Mockito.mock方法,到MockitoCore的mock方法

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.build(typeToMock);
    //创建mock对象
    T mock = createMock(creationSettings);
    mockingProgress().mockingStarted(mock, creationSettings);
    return mock;
}

MockUtil的createMock方法

public static <T> T createMock(MockCreationSettings<T> settings) {
    //创建一个MockHandler,很关键的一个类
    MockHandler mockHandler =  createMockHandler(settings);
    //创建mock对象,这里是调用SubclassByteBuddyMockMaker的createMock
    T mock = mockMaker.createMock(settings, mockHandler);

    Object spiedInstance = settings.getSpiedInstance();
    if (spiedInstance != null) {
        new LenientCopyTool().copyToMock(spiedInstance, mock);
    }
    return mock;
}

//MockHandlerFactory
//这里包了很多层,但是handle最终是调用MockHandlerImpl的handle方法
//MockHandlerImpl是核心中核心类,等下会讲到
public static <T> MockHandler<T> createMockHandler(MockCreationSettings<T> settings) {
    MockHandler<T> handler = new MockHandlerImpl<T>(settings);
    MockHandler<T> nullResultGuardian = new NullResultGuardian<T>(handler);
    return new InvocationNotifierHandler<T>(nullResultGuardian, settings);
}
SubclassByteBuddyMockMaker
@Override
public <T> T createMock(MockCreationSettings<T> settings, MockHandler handler) {
    //生成代理类,调用SubclassBytecodeGenerator的mockClass
    Class<? extends T> mockedProxyType = createMockType(settings);

    Instantiator instantiator = Plugins.getInstantiatorProvider().getInstantiator(settings);
    T mockInstance = null;
    try {
        //为代理类生成实例对象
        mockInstance = instantiator.newInstance(mockedProxyType);
        MockAccess mockAccess = (MockAccess) mockInstance;
        //MockMethodInterceptor-代理类的拦截器
        mockAccess.setMockitoInterceptor(new MockMethodInterceptor(handler, settings));

        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
@Override
public <T> Class<? extends T> mockClass(MockFeatures<T> features) {
    String name = nameFor(features.mockedType);
    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(to(DispatcherDefaultingToRealMethod.class))
                       .transform(withModifiers(SynchronizationState.PLAIN))
                       .attribute(features.stripAnnotations
                           ? MethodAttributeAppender.NoOp.INSTANCE
                           : 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);
    }
    ClassLoader classLoader = 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());
    if (classLoader != features.mockedType.getClassLoader()) {
        assertVisibility(features.mockedType);
        for (Class<?> iFace : features.interfaces) {
            assertVisibility(iFace);
        }
        builder = builder.ignoreAlso(isPackagePrivate()
            .or(returns(isPackagePrivate()))
            .or(hasParameters(whereAny(hasType(isPackagePrivate())))));
    }
    return builder.make()
                  .load(classLoader, loader.resolveStrategy(features.mockedType, classLoader, name.startsWith(CODEGEN_PACKAGE)))
                  .getLoaded();
}

生成代理类的关键代码。Mockito使用的是ByteBuddy这个框架,它并不需要编译器的帮助,而是直接生成class,然后使用ClassLoader来进行加载,感兴趣的可以深入研究,其地址为:https://github.com/raphw/byte-buddy。如果你有使用过其他字节码生成框架,如Cglib或Javassist,就可以大概猜到这里做了什么事情。这里很重要的一点就是把MockMethodInterceptor作为代理类的拦截器。最后就是生成字节码然后动态加载到JVM中。

Mockito生成的代理类

那么Mockito利用ByteBuddy生成的代理类是长怎么样子的,才能知道它是怎么做拦截的。就好像你看到JDK Proxy生成的代理类,就会更清楚InvocationHandler的实现一样。还是文章开头的例子

我们可以看到几点关键的

  • 持有一个MockMethodInterceptor对象
  • 继承了MockAccess
  • List原来的方法都被DispatcherDefaultingToRealMethod拦截了

代理拦截interceptor

好,我们现在有目标了。DispatcherDefaultingToRealMethodMockMethodInterceptor的内部类,最终它还是调用MockMethodInterceptor的doIntercept

Object doIntercept(Object mock,
                   Method invokedMethod,
                   Object[] arguments,
                   RealMethod realMethod,
                   Location location) throws Throwable {
    //最终调用的是MockHandler的handle方法
    //MockHandler最终还是调用MockHandlerImpl的,上面提过了。
    //MockHandlerImpl是核心,下面会讲
    return handler.handle(createInvocation(mock, invokedMethod, arguments, realMethod, mockCreationSettings, location));
}

verify

说了mock对象是如何生成的,就可以开始说用法的实现了。上面我们猜测verify是对调用做了记录,下面我们来证实一下。

verify start

Mockito的verify

@CheckReturnValue
public static <T> T verify(T mock) {
    return MOCKITO_CORE.verify(mock, times(1));
}
@CheckReturnValue
public static VerificationMode times(int wantedNumberOfInvocations) {
    //通过VerificationModeFactory创建了一个VerificationMode
    //VerificationMode在verify行为中是一个重要的概念。这里times表示次数,因为verify还可以校验调用了多少次
    return VerificationModeFactory.times(wantedNumberOfInvocations);
}

VerificationMode有很多中类型,代表了verify的种类。

从名字中大概可以猜到有什么用,大家可以去尝试一下。

继续跟踪verify到MockitoCore的verify

public <T> T verify(T mock, VerificationMode mode) {
    if (mock == null) {
        throw nullPassedToVerify();
    }
    MockingDetails mockingDetails = mockingDetails(mock);
    if (!mockingDetails.isMock()) {
        throw notAMockPassedToVerify(mock.getClass());
    }
    MockHandler handler = mockingDetails.getMockHandler();
    //通知listener
    mock = (T) VerificationStartedNotifier.notifyVerificationStarted(
        handler.getMockSettings().getVerificationStartedListeners(), mockingDetails);
    //初始化ThreadLocal,这里是MockingProgressImpl。这个对象的作用可以理解为mock的“进度”,无论verify还是stub都经过它
    MockingProgress mockingProgress = mockingProgress();
    
    VerificationMode actualMode = mockingProgress.maybeVerifyLazily(mode);
    //标记verify开始。new一个MockAwareVerificationMode,实际还是开头创建的那个VerificationMode,只是封装了一下。
    mockingProgress.verificationStarted(new MockAwareVerificationMode(mock, actualMode, mockingProgress.verificationListeners()));
    return mock;
}

MockingProgressImpl的verificationStarted

public void verificationStarted(VerificationMode verify) {
    //校验一下状态
    validateState();
    //还会重置stub
    resetOngoingStubbing();
    //把MockingProgressImpl的verificationMode设置为开头创建的那个
    verificationMode = new Localized(verify);
}

这里注意,MockingProgressImpl是ThreadLocal的,而且它的verificationMode只有一个,就说明verify之后紧接着的mock调用就是针对这次verify的,如果多次verify的话,后者会覆盖前者。verify start就这么多了,最重要的就是记录VerificationMode

verify match

记录了VerificationMode,那么如果下次调用怎么去匹配是刚刚的verify的呢?上面说了所有mock对象的方法调用都会被MockMethodInterceptor拦截,而MockMethodInterceptor最终会调到MockHandlerImpl的handle方法,一直憋了很久的MockHandlerImpl终于要出场了。这里除了verify,还有stub的,因为这个方法是包含了mock相关的所有核心逻辑了。

//Invocation即InterceptedInvocation,把代理类拦截时的方法调用即参数封装在一起
//它包含一下几个对象:真正的方法realMethod,Mockito的方法MockitoMethod,参数arguments,以及mock对象mockRef
/** 
    private final MockReference<Object> mockRef;
    private final MockitoMethod mockitoMethod;
    private final Object[] arguments, rawArguments;
    private final RealMethod realMethod;
*/
public Object handle(Invocation invocation) throws Throwable {
        //stub
        if (invocationContainer.hasAnswersForStubbing()) {
            // stubbing voids with doThrow() or doAnswer() style
            InvocationMatcher invocationMatcher = matchersBinder.bindMatchers(
                    mockingProgress().getArgumentMatcherStorage(),
                    invocation
            );
            invocationContainer.setMethodForStubbing(invocationMatcher);
            return null;
        }
        //获取VerificationMode,VerificationMode至多一个
        VerificationMode verificationMode = mockingProgress().pullVerificationMode();
        
        InvocationMatcher invocationMatcher = matchersBinder.bindMatchers(
                mockingProgress().getArgumentMatcherStorage(),
                invocation
        );
        //校验状态,如果处于不争取的状态,会抛异常
        mockingProgress().validateState();

        //如果之前调用过verify方法的话,这里就不为null
        // 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(invocationContainer, invocationMatcher);
                //最终调用VerificationMode的verify去验证
                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);
        OngoingStubbingImpl<T> ongoingStubbing = new OngoingStubbingImpl<T>(invocationContainer);
        //记下ongoingStubbing,每次调用都会刷新最新的ongoingStubbing
        mockingProgress().reportOngoingStubbing(ongoingStubbing);

        // look for existing answer for this invocation
        StubbedInvocationMatcher stubbing = invocationContainer.findAnswerFor(invocation);
        notifyStubbedAnswerLookup(invocation, stubbing);

        if (stubbing != null) {
            stubbing.captureArgumentsFrom(invocation);
            try {
                //这里是对stub的返回,即thenReturn的返回,下面的代码会讲到
                return stubbing.answer(invocation);
            } finally {
                //Needed so that we correctly isolate stubbings in some scenarios
                //see MockitoStubbedCallInAnswerTest or issue #1279
                mockingProgress().reportOngoingStubbing(ongoingStubbing);
            }
        } else {
            //下面代码意思是对mock对象的默认返回
            //测试中我们知道,mock对象的每一个操作都是“无效”的,而且都会返回一个相应类型的默认值(stub除外)。
            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;
        }
    }

好,我们看看Times这个VerificationMode的verify方法

@Override
public void verify(VerificationData data) {
    //获取所有的调用
    List<Invocation> invocations = data.getAllInvocations();
    //获取的verify的调用
    MatchableInvocation wanted = data.getTarget();

    if (wantedCount > 0) {
        //两者做匹配
        checkMissingInvocation(data.getAllInvocations(), data.getTarget());
    }
    checkNumberOfInvocations(invocations, wanted, wantedCount);
}
MissingInvocationChecker
public static void checkMissingInvocation(List<Invocation> invocations, MatchableInvocation wanted) {
    //匹配所有的调用和目标调用
    List<Invocation> actualInvocations = findInvocations(invocations, wanted);
    //如果找到则“安全”返回return
    if (!actualInvocations.isEmpty()){
        return;
    }
    //否则会抛出异常

    Invocation similar = findSimilarInvocation(invocations, wanted);
    if (similar == null) {
        throw wantedButNotInvoked(wanted, invocations);
    }

    Integer[] indexesOfSuspiciousArgs = getSuspiciouslyNotMatchingArgsIndexes(wanted.getMatchers(), similar.getArguments());
    SmartPrinter smartPrinter = new SmartPrinter(wanted, similar, indexesOfSuspiciousArgs);
    throw argumentsAreDifferent(smartPrinter.getWanted(), smartPrinter.getActual(), similar.getLocation());
}

通过调试发现在InvocationMatcher判断两个Invocation是否匹配

@Override
    public boolean matches(Invocation candidate) {
        //可以看出就是通过判断mock对象是否一样,方法及参数是否一样
        return invocation.getMock().equals(candidate.getMock()) && hasSameMethod(candidate) && argumentsMatch(candidate);
    }

verify的过程就是这样了,如果找到匹配的Invocation则正常返回,找不到Mockito是会抛出异常提示信息,像assert那样子,大家可以尝试一下。

stub

stub就是可以指定返回,一般我们用的更多,从上面verify的实现过程,我们应该也能大概猜到stub也有点类似。

when

跟踪when方法直到MockitoCore的when

public <T> OngoingStubbing<T> when(T methodCall) {
    //初始化MockingProgressImpl
    MockingProgress mockingProgress = mockingProgress();
    //标记stub开始
    mockingProgress.stubbingStarted();
    //获取最新的ongoingStubbing,这个ongoingStubbing会在每次MockHandlerImpl的handle都会新建一个,然后set到ThreadLocal的mockingProgress中,所以这里取出来的就是上一次的调用,这里也证明了其实when的参数是没用的,只要mock对象有方法调用就可以了。
    @SuppressWarnings("unchecked")
    OngoingStubbing<T> stubbing = (OngoingStubbing<T>) mockingProgress.pullOngoingStubbing();
    if (stubbing == null) {
        mockingProgress.reset();
        throw missingMethodInvocation();
    }
    //返回OngoingStubbing
    return stubbing;
}

when方法就是返回上次mock方法调用封装好的OngoingStubbing。

thenReturn

thenReturn是BaseStubbing的方法,其实它也是一个OngoingStubbing OngoingStubbing的继承关系

@Override
public OngoingStubbing<T> thenReturn(T value) {
    return thenAnswer(new Returns(value));
}

//OngoingStubbingImpl
@Override
public OngoingStubbing<T> thenAnswer(Answer<?> answer) {
    if(!invocationContainer.hasInvocationForPotentialStubbing()) {
        throw incorrectUseOfApi();
    }
    //把这个answer加入到
    invocationContainer.addAnswer(answer);
    return new ConsecutiveStubbing<T>(invocationContainer);
}

//InvocationContainerImpl
public StubbedInvocationMatcher addAnswer(Answer answer, boolean isConsecutive) {
    //获取stub的调用
    Invocation invocation = invocationForStubbing.getInvocation();
    //标记stub完成
    mockingProgress().stubbingCompleted();
    if (answer instanceof ValidableAnswer) {
        ((ValidableAnswer) answer).validateFor(invocation);
    }
    //把stub的调用和answer加到StubbedInvocationMatcher的list
    //这里的意思就是把调用和返回绑定,如果下次调用匹配到了,就返回对应的answer
    synchronized (stubbed) {
        if (isConsecutive) {
            stubbed.getFirst().addAnswer(answer);
        } else {
            stubbed.addFirst(new StubbedInvocationMatcher(invocationForStubbing, answer));
        }
        return stubbed.getFirst();
    }
}

那么下次的调用怎么匹配到answer呢?还是在MockHandlerImpl的handle,这里只截图相关代码

if (stubbing != null) {
        stubbing.captureArgumentsFrom(invocation);
        try {
            //就是返回stub的answer方法
            return stubbing.answer(invocation);
        } finally {
            //Needed so that we correctly isolate stubbings in some scenarios
            //see MockitoStubbedCallInAnswerTest or issue #1279
            mockingProgress().reportOngoingStubbing(ongoingStubbing);
        }
    }

//StubbedInvocationMatcher
public Object answer(InvocationOnMock invocation) throws Throwable {
    //see ThreadsShareGenerouslyStubbedMockTest
    Answer a;
    //从之前放进来的answers获取最新的那个
    synchronized(answers) {
        a = answers.size() == 1 ? answers.peek() : answers.poll();
    }
    return a.answer(invocation);
}

先看看Answer的继承关系,看看Mockito支持什么样的对stub的返回


我们用回刚刚的例子,对应的Answer就是Returns

public Object answer(InvocationOnMock invocation) throws Throwable {
    //就是直接返回我们指定的值
    return value;
}

优点

Mockito最大的优点是其简单,实用,优雅的API,这是作者的初衷。Java的Mock工具还有JMock,easymock等等,大家可以对比一下。

总结

本文介绍了Mockito的基本使用及注意事项,相信看了之后你已经可以在日常工作中用上。还源码分析了其实现mock的原理,通过源码看到,表面看上去挺神奇的一个东西,其实现也不是很难的,主要用到代理+状态来控制mock对象。

参考资料

https://infoq.cn/article/mockito-design https://blog.saymagic.cn/2016/09/17/understand-mockito.html


墙裂推荐

【深度】互联网技术人的社群,点击了解!





● Slf4j适配日志原理

● SpringBoot2.x 官方推荐缓存框架-Caffeine高性能设计剖析

● Eureka客户端和服务端的到底是如何通讯的?

● Java插入式注解处理API科普



关注公众号,回复“spring”有惊喜!!!

如果资源对你有帮助的话

❤️给个「在看」,是最大的支持❤️


    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存