单元测试源码分析之二Mockito自动装配和插桩

前面分析过了Mockito是如何通过注解创建对应的mock对象,并对其所有方法设置拦截器。这篇博客再分析下Mockito是如何实现自动装配和插桩的,只有更好的了解其原理,在写复杂单测的时候才能更加得心应手。

@InjectMocks的自动装配

InjectingAnnotationEngine.java

    public void process(Class<?> clazz, Object testInstance) {
        //创建mock对象或者spy对象
        processIndependentAnnotations(testInstance.getClass(), testInstance);
        //对添加了@InjectMocks注解的对象进行自动装配
        processInjectMocks(testInstance.getClass(), testInstance);
    }

之前详细分析了processIndependentAnnotations(final Class<?> clazz, final Object testInstance源码,现在再看看processInjectMocks(final Class<?> clazz, final Object testInstance) 这部分源码

InjectingAnnotationEngine.java

     private void processInjectMocks(final Class<?> clazz, final Object testInstance) {
        Class<?> classContext = clazz;
        //循环,从当前测试类对象开始执行自动注入的方法,一直到所有的非Object的父类都注入完成
        while (classContext != Object.class) {
            injectMocks(testInstance);
            classContext = classContext.getSuperclass();
        }
     }
     public void injectMocks(final Object testClassInstance) {
        //获得当前测试类Class
        Class<?> clazz = testClassInstance.getClass();
        Set<Field> mockDependentFields = new HashSet<Field>();
        Set<Object> mocks = newMockSafeHashSet();

        while (clazz != Object.class) {
            //[1]从当前测试类开始遍历找到添加了@InjectMock属性加入到mockDependentFields集合 (一直向上遍历,直到Object)
            new InjectMocksScanner(clazz).addTo(mockDependentFields);
            //从当前测试类开始遍历找到添加了@Mock属性加入到mocks集合
            new MockScanner(testClassInstance, clazz).addPreparedMocks(mocks);
            //这里是个钩子方法,没有实现
            onInjection(testClassInstance, clazz, mockDependentFields, mocks);
            clazz = clazz.getSuperclass();
        }
        //[2]真正对添加了@InjectMocks注解的对象进行自动注入
        new DefaultInjectionEngine().injectMocksOnFields(mockDependentFields, mocks, testClassInstance);
    }
[1]InjectMocksScanner和MockScanner

InjectMocksScanner.java就是通过反射拿到clazz的所有属性,将添加了@InjectMocks注解但是没有加@Mock和@Captor的属性都添加到mockDependentFields集合中 ,从源码可以看出 : 不能同时对一个属性添加@Mock和@InjectMocks注解,否则会抛出异常

MockScanner同理,就是拿到clazz下所有添加了@Spy或者@Mock注解的对象(也可以是通过api生成的mock或者spy对对象)

    public InjectMocksScanner(Class<?> clazz) {
        this.clazz = clazz;
    }
    public void addTo(Set<Field> mockDependentFields) {
        //将遍历到的满足条件的属性都加入到参数集合中
        mockDependentFields.addAll(scan());
    }
    private Set<Field> scan() {
        Set<Field> mockDependentFields = new HashSet<Field>();
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            //将添加了@InjectMocks并且没有加上@Mock和@Captor的属性都加入到集合中
            if (null != field.getAnnotation(InjectMocks.class)) {
                assertNoAnnotations(field, Mock.class, Captor.class);
                mockDependentFields.add(field);
            }
        }
        return mockDependentFields;
    }
[2]injectMocksOnFields 自动注入

创建MockInjection对象,顺便指定注入策略(构造器注入和属性注入),以及注入后置处理器,完成对@InjectMocks属性的处理

DefaultInjectionEngine.java

     private final MockInjectionStrategy injectionStrategies = MockInjectionStrategy.nop();

     public void injectMocksOnFields(Set<Field> needingInjection, Set<Object> mocks, Object testClassInstance) {
        //分别将needingInjection和testClassInstance赋值给OngoingMockInjection的fields和fieldOwner字段
        MockInjection.onFields(needingInjection, testClassInstance)
                //赋值给给OngoingMockInjection的mocks字段
                .withMocks(mocks)
                //向injectionStrategies注入策略添加构造器注入ConstructorInjection
                .tryConstructorInjection()
                //向injectionStrategies注入策略添加属性注入PropertyAndSetterInjection(这是一个责任链模式)
                .tryPropertyOrFieldInjection()
                //给postInjectionStrategies添加SpyOnInjectedFieldsHandler(将InjectsMock的属性采用spy方法初始化??)
                .handleSpyAnnotation()
                //执行属性注入和注入后的后置处理
                .apply();
    }
    //MockInjection.java
    public void apply() {
       for (Field field : fields) {
           injectionStrategies.process(field, fieldOwner, mocks);
           postInjectionStrategies.process(field, fieldOwner, mocks);
       }
    }
    //MockInjectionStrategy.java
    public boolean process(Field onField, Object fieldOwnedBy, Set<Object> mockCandidates) {
        //先执行构造器注入,如果失败则获取下一个策略--属性注入
        if(processInjection(onField, fieldOwnedBy, mockCandidates)) {
            return true;
        }
        return relayProcessToNextStrategy(onField, fieldOwnedBy, mockCandidates);
    }

分别看下两个注入策略
ConstructorInjection.java

 public boolean processInjection(Field field, Object fieldOwner, Set<Object> mockCandidates) {
        try {
            SimpleArgumentResolver simpleArgumentResolver = new SimpleArgumentResolver(mockCandidates);
            //构建其FieldInitialization进行初始化(由于在测试类里没有带参数的构造函数,所以这里会走到异常)
            FieldInitializationReport report = new FieldInitializer(fieldOwner, field, simpleArgumentResolver).initialize();
            return report.fieldWasInitializedUsingContructorArgs();
        } catch (MockitoException e) {
            if(e.getCause() instanceof InvocationTargetException) {
                Throwable realCause = e.getCause().getCause();
                throw fieldInitialisationThrewException(field, realCause);
            }
            // other causes should be fine
            return false;
        }

    }

PropertyAndSetterInjection.java

     private final MockCandidateFilter mockCandidateFilter =
            new TypeBasedCandidateFilter(
                    new NameBasedCandidateFilter(
                            new TerminalMockCandidateFilter()));
  
    public boolean processInjection(Field injectMocksField, Object injectMocksFieldOwner, Set<Object> mockCandidates) {
        //创建属性构造器
        FieldInitializationReport report = initializeInjectMocksField(injectMocksField, injectMocksFieldOwner);

        // for each field in the class hierarchy
        boolean injectionOccurred = false;
        //这里的fieldClass就是加了@InjectMocks注解的字段类型
        Class<?> fieldClass = report.fieldClass();
        //添加了@InjectMocks注解的字段的实例
        Object fieldInstanceNeedingInjection = report.fieldInstance();
        while (fieldClass != Object.class) {
            injectionOccurred |= injectMockCandidates(fieldClass, fieldInstanceNeedingInjection, newMockSafeHashSet(mockCandidates));
            fieldClass = fieldClass.getSuperclass();
        }
        return injectionOccurred;
    }
    private boolean injectMockCandidates(Class<?> awaitingInjectionClazz, Object injectee, Set<Object> mocks) {
        boolean injectionOccurred;
        //获取@InjectMocks对象的字段信息
        List<Field> orderedCandidateInjecteeFields = orderedInstanceFieldsFrom(awaitingInjectionClazz);
        //[1] 执行结果为true
        injectionOccurred = injectMockCandidatesOnFields(mocks, injectee, false, orderedCandidateInjecteeFields);
        //[2] 由于orderedCandidateInjecteeFields只有一个待被mock对象注入的属性,所以这一步没有执行什么逻辑,返回默认true,按位相或 也是true.
        injectionOccurred |= injectMockCandidatesOnFields(mocks, injectee, injectionOccurred, orderedCandidateInjecteeFields);
        return injectionOccurred;
    }
    
    //[1],[2]
    private boolean injectMockCandidatesOnFields(Set<Object> mocks,
                                                 Object injectee,
                                                 boolean injectionOccurred,
                                                 List<Field> orderedCandidateInjecteeFields) {
        //遍历被注入对象的属性                                         
        for (Iterator<Field> it = orderedCandidateInjecteeFields.iterator(); it.hasNext(); ) {
            Field candidateField = it.next();
            //匹配mock对象和待注入对象的属性
            //TypeBasedCandidateFilter Class类型匹配,满足则进行下一步
            //NameBasedCandidateFilter 判断mockName是否相同,是否没有同类型的其他字段需要注入 这里第一次[1]返回false
            //TerminalMockCandidateFilter 对mock集合只有一个mock对象的情形进行注入
            Object injected = mockCandidateFilter.filterCandidate(mocks, candidateField, orderedCandidateInjecteeFields, injectee)
                                                 .thenInject();
            //mock对象被注入成功之后从mock对象集合中移除                                     
            if (injected != null) {
                injectionOccurred |= true;
                mocks.remove(injected);
                it.remove();
            }
        }
        return injectionOccurred;
    }

TerminalMockCandidateFilter.java

  public OngoingInjector filterCandidate(final Collection<Object> mocks,
                                           final Field candidateFieldToBeInjected,
                                           final List<Field> allRemainingCandidateFields,
                                           final Object injectee) {
        //集合里的mock对象只有1个                                    
        if(mocks.size() == 1) {
            final Object matchingMock = mocks.iterator().next();
            //返回一个匿名内部类的对象
            return new OngoingInjector() {
                public Object thenInject() {
                    try {
                        //这就是真正进行注入的方法,先通过属性的setter方法进行反射赋值;
                        //如果失败则通过反射直接对属性赋值
                        if (!new BeanPropertySetter(injectee, candidateFieldToBeInjected).set(matchingMock)) {
                            setField(injectee, candidateFieldToBeInjected,matchingMock);
                        }
                    } catch (RuntimeException e) {
                        throw cannotInjectDependency(candidateFieldToBeInjected, matchingMock, e);
                    }
                    return matchingMock;
                }
            };
        }
        return OngoingInjector.nop;
    }

插桩Stubbing

之前简单分析过Mockito是怎么让mock对象调用方法都进入自己的拦截器,主要是在通过ByteBuddy生成mock代理类的时候定义了相关的拦截器

接下来再看看Mockito中最核心的Stubbing的实现

   Mockito.when(userDao.queryUserByName(eq("chenpp"))).thenReturn(list);
Stub之When方法

先来看下When的作用,这里也是进入了MOCKITO_CORE的对应方法,其代码都是围绕mockingProgress这个对象展开的,可以看到这是从ThreadLocal里获取到的一个对象, 调用其pullOngoingStubbing方法后获得一个OngoingStubbing的返回对象,也就是这个方法的返回值。可以看到when方法的入参是mock对象的某个调用方法的返回值,那么来看看这个入参到底是什么呢?
之前一篇博客里提到过mock对象的所有方法调用都会被拦截,进入到MockHandlerImpl的handle方法,再来仔细看看这个类的源码

    @CheckReturnValue
    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;
    }

ThreadSafeMockingProgress.java

  private static final ThreadLocal<MockingProgress> MOCKING_PROGRESS_PROVIDER = new          ThreadLocal<MockingProgress>() {
        @Override
        protected MockingProgress initialValue() {
            return new MockingProgressImpl();
        }
    };
    
public final static MockingProgress mockingProgress() {
        return MOCKING_PROGRESS_PROVIDER.get();
    }

这里的mockingProgress实际是一个MockingProgressImpl的实例对象,具体的属性有什么等到后面使用到的时候再看

MockingProgressImpl.java

    private final ArgumentMatcherStorage argumentMatcherStorage = new ArgumentMatcherStorageImpl();

    private OngoingStubbing<?> ongoingStubbing;
    private Localized<VerificationMode> verificationMode;
    private Location stubbingInProgress = null;
    private VerificationStrategy verificationStrategy;
    private final Set<MockitoListener> listeners = new LinkedHashSet<MockitoListener>();

    public MockingProgressImpl() {
        this.verificationStrategy = getDefaultVerificationStrategy();
    }

    public static VerificationStrategy getDefaultVerificationStrategy() {
        return new VerificationStrategy() {
            public VerificationMode maybeVerifyLazily(VerificationMode mode) {
                return mode;
            }
        };
    }
MockHandlerImpl 拦截器进入的类

这里的Invocation即InterceptedInvocation,把代理类拦截时的方法调用和参数封装在一起,它包含以下几个对象:真正的方法realMethod,Mockito的方法MockitoMethod,参数arguments,以及mock对象mockRef等

  public Object handle(Invocation invocation) throws Throwable {
        //判断当前mock对象是否已插桩(在执行when的时候,还没有被插桩所有不会执行该分支)
        if (invocationContainer.hasAnswersForStubbing()) {
            // stubbing voids with doThrow() or doAnswer() style
            InvocationMatcher invocationMatcher = matchersBinder.bindMatchers(
                    mockingProgress().getArgumentMatcherStorage(),
                    invocation
            );
            invocationContainer.setMethodForStubbing(invocationMatcher);
            return null;
        }
        //获取verify信息
        VerificationMode verificationMode = mockingProgress().pullVerificationMode();
        //invocationMatcher里包含invocation信息以及参数匹配信息(bindMatchers执行之后会将之前的信息返回并且清空ArgumentMatcherStorageImpl存储匹配信息的matcherStack)
        InvocationMatcher invocationMatcher = matchersBinder.bindMatchers(
                //ArgumentMatcherStorageImpl的实例对象,保存参数匹配的相关信息
                mockingProgress().getArgumentMatcherStorage(),
                invocation
        );
        //验证当前MockingProgressImpl状态是否正常,比方说matcherStack是否已经被重置清理等
        mockingProgress().validateState();

        // verificationMode 只有当有人在操作verify的是才会有值,插桩和mock的是都是null
        if (verificationMode != null) {
            // 判断verification的mock对象和在使用的mock对象是否是一个
            if (((MockAwareVerificationMode) verificationMode).getMock() == invocation.getMock()) {
                VerificationDataImpl data = new VerificationDataImpl(invocationContainer, invocationMatcher);
                verificationMode.verify(data);
                return null;
            } else {
                // 当前的invocation是另一个mock对象的,将verify信息重新加回去
                //this means there is an invocation on a different mock. Re-adding verification mode
                mockingProgress().verificationStarted(verificationMode);
            }
        }

        // prepare invocation for stubbing
        invocationContainer.setInvocationForPotentialStubbing(invocationMatcher);
        OngoingStubbingImpl<T> ongoingStubbing = new OngoingStubbingImpl<T>(invocationContainer);
        //将ongoingStubbing赋值给MockingProgressImpl对象的ongoingStubbing属性,每次调用都会获取到最新的ongoingStubbing
        mockingProgress().reportOngoingStubbing(ongoingStubbing);
        //根据invocation查找对应的answer(在插桩的时候stubbed是空的)
        // look for existing answer for this invocation
        StubbedInvocationMatcher stubbing = invocationContainer.findAnswerFor(invocation);
        // 通知回调,通知listener when completed, we should be able to get rid of the casting below
        notifyStubbedAnswerLookup(invocation, stubbing, invocationContainer.getStubbingsAscending(),
                                  (CreationSettings) mockSettings);
        //插桩时stubbing为null
        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);
            }
        } else {
            //获取默认的answer信息
            Object ret = mockSettings.getDefaultAnswer().answer(invocation);
            //验证answer类型和对应的方法返回值是否匹配
            DefaultAnswerValidator.validateReturnValueFor(invocation, ret);

            //Mockito uses it to redo setting invocation for potential stubbing in case of partial mocks / spies.
            //为下一次可能的stub重置invocation (将invocationMatcher赋值给InvocationContainerImpl的invocationForStubbing属性)
            invocationContainer.resetInvocationForPotentialStubbing(invocationMatcher);
            return ret;
        }
    }

再看下之前when里的源码就很清楚了,通过when(mockObj.methdo())方法调用,我们可以拿到在MockHandlerImpl创建的ongoingStubbing对象,里面包含invocation和我们调用方法的参数匹配信息

   //MockitoCore 
   public <T> OngoingStubbing<T> when(T methodCall) {
        //获取MockingProgressImpl实例对象
        MockingProgress mockingProgress = mockingProgress();
        //验证mockingProgress状态并且给stubbingInProgress属性赋值(即标记开始插桩)
        mockingProgress.stubbingStarted();
        @SuppressWarnings("unchecked")
        //获取ongoingStubbing对象,这是在调用mock对象的方法时赋值的,包含invocation信息以及参数匹配信息
        OngoingStubbing<T> stubbing = (OngoingStubbing<T>) mockingProgress.pullOngoingStubbing();
        if (stubbing == null) {
            mockingProgress.reset();
            throw missingMethodInvocation();
        }
        return stubbing;
    }
Stub之ThenReturn

将返回结果包装成answer,通过InvocationContainerImpl的addAnswer方法将stub和返回绑定

    public OngoingStubbing<T> thenReturn(T value) {
        return thenAnswer(new Returns(value));
    }
    
    private final InvocationContainerImpl invocationContainer;

    @Override
    public OngoingStubbing<T> thenAnswer(Answer<?> answer) {
        if(!invocationContainer.hasInvocationForPotentialStubbing()) {
            throw incorrectUseOfApi();
        }
        //向invocationContainer添加answer(这里的answer就是Return对象,包含我们创建的返回值对象)
        invocationContainer.addAnswer(answer, strictness);
        return new ConsecutiveStubbing<T>(invocationContainer);
    }

InvocationContainerImpl.java

    public void addAnswer(Answer answer, Strictness stubbingStrictness) {
        registeredInvocations.removeLast();
        addAnswer(answer, false, stubbingStrictness);
    }
    
   /**
     * Adds new stubbed answer and returns the invocation matcher the answer was added to.
     * @param isConsecutive连续不断的
     */
    public StubbedInvocationMatcher addAnswer(Answer answer, boolean isConsecutive, Strictness stubbingStrictness) {
        Invocation invocation = invocationForStubbing.getInvocation();
        //标记完成插桩
        mockingProgress().stubbingCompleted();
        if (answer instanceof ValidableAnswer) {
             //验证设定的返回值是否正确
            ((ValidableAnswer) answer).validateFor(invocation);
        }

        synchronized (stubbed) {
            //这里就是把调用和返回绑定,如果下次调用匹配到了,就返回对应的answer
            if (isConsecutive) {
                stubbed.getFirst().addAnswer(answer);
            } else {
                //这里设置的answer并不是连续的,所以直接加到stubbed(LinkedList)的头部就行
                Strictness effectiveStrictness = stubbingStrictness != null ? stubbingStrictness : this.mockStrictness;
                //这里的invocationForStubbing
                stubbed.addFirst(new StubbedInvocationMatcher(answer, invocationForStubbing, effectiveStrictness));
            }
            return stubbed.getFirst();
        }
    }

这样在单元测试调用Mock对象的方法时,会再次进入MockHandlerImpl的handle方法

    public StubbedInvocationMatcher findAnswerFor(Invocation invocation) {
        synchronized (stubbed) {
            for (StubbedInvocationMatcher s : stubbed) {
                //匹配mock对象,方法信息,参数信息,如果都匹配上则说明是这个answer
                if (s.matches(invocation)) {
                    s.markStubUsed(invocation);
                    //标记对应的stub信息 we should mark stubbed at the point of stubbing, not at the point where the stub is being used
                    invocation.markStubbed(new StubInfoImpl(s));
                    return s;
                }
            }
        }
        return null;
    }
    
    @Override
    public boolean matches(Invocation candidate) {
        return invocation.getMock().equals(candidate.getMock()) && hasSameMethod(candidate) && argumentsMatch(candidate);
    }

最后执行StubbedInvocationMatcher的answer方法获取最终的返回结果,如下:

  public Object answer(InvocationOnMock invocation) throws Throwable {
        //see ThreadsShareGenerouslyStubbedMockTest
        Answer a;
        synchronized(answers) {
            //这里是从answers中获取其中一个,如果只有一个则使用peek(),否则使用poll
            //peek表示获取队列的队头元素但是并不移除,poll表示获取并移除队头元素(这是为了可以进行多次mock以及设置相同参数的每次不同的返回值--不了解的可以看下我之前关于Mockito使用的博客)
            a = answers.size() == 1 ? answers.peek() : answers.poll();
        }
        //就是返回Returns封装的value值
        return a.answer(invocation);
    }

Verify

最后再简单讲下verify的原理,Mockito.verify(userDao, Mockito.times(1))返回的还是一开始的mock对象,所以后续方法调用依旧会走到MockHandlerImpl的handle方法,此时VerificationMode就不为null了

    public <T> T verify(T mock, VerificationMode mode) {
        if (mock == null) {
            throw nullPassedToVerify();
        }
        //获取mock详细信息
        MockingDetails mockingDetails = mockingDetails(mock);
        if (!mockingDetails.isMock()) {
            throw notAMockPassedToVerify(mock.getClass());
        }
        assertNotStubOnlyMock(mock);
        //拿到mockHandler
        MockHandler handler = mockingDetails.getMockHandler();
        //调用VerificationStarted的通知回调
        mock = (T) VerificationStartedNotifier.notifyVerificationStarted(
            handler.getMockSettings().getVerificationStartedListeners(), mockingDetails);
        MockingProgress mockingProgress = mockingProgress();
        VerificationMode actualMode = mockingProgress.maybeVerifyLazily(mode);
        //封装MockAwareVerificationMode对象并赋值给MockingProgressImpl的verificationMode字段属性
        mockingProgress.verificationStarted(new MockAwareVerificationMode(mock, actualMode, mockingProgress.verificationListeners()));
        return mock;
    }

当VerificationMode不为空时,先验证mode的状态是否正确,验证通过后获取到对应的container和invocationMatcher信息,最后调用verificationMode的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 = 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);
            }
        }

至此,Mockito主要的原理分析都完成了

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值