单元测试源码分析之二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) {//獲得當前測試類ClassClass<?> 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.javapublic void apply() {for (Field field : fields) {injectionStrategies.process(field, fieldOwner, mocks);postInjectionStrategies.process(field, fieldOwner, mocks);}}//MockInjectionStrategy.javapublic boolean process(Field onField, Object fieldOwnedBy, Set<Object> mockCandidates) {//先執行構造器注入,如果失敗則獲取下一個策略--屬性注入if(processInjection(onField, fieldOwnedBy, mockCandidates)) {return true;}return relayProcessToNextStrategy(onField, fieldOwnedBy, mockCandidates);}分別看下兩個注入策略
ConstructorInjection.java
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 hierarchyboolean 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] 執行結果為trueinjectionOccurred = 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方法,再來仔細看看這個類的源碼
ThreadSafeMockingProgress.java
private static final ThreadLocal<MockingProgress> MOCKING_PROGRESS_PROVIDER = new ThreadLocal<MockingProgress>() {@Overrideprotected 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() styleInvocationMatcher 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的是都是nullif (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 modemockingProgress().verificationStarted(verificationMode);}}// prepare invocation for stubbinginvocationContainer.setInvocationForPotentialStubbing(invocationMatcher);OngoingStubbingImpl<T> ongoingStubbing = new OngoingStubbingImpl<T>(invocationContainer);//將ongoingStubbing賦值給MockingProgressImpl對象的ongoingStubbing屬性,每次調用都會獲取到最新的ongoingStubbingmockingProgress().reportOngoingStubbing(ongoingStubbing);//根據invocation查找對應的answer(在插樁的時候stubbed是空的)// look for existing answer for this invocationStubbedInvocationMatcher stubbing = invocationContainer.findAnswerFor(invocation);// 通知回調,通知listener when completed, we should be able to get rid of the casting belownotifyStubbedAnswerLookup(invocation, stubbing, invocationContainer.getStubbingsAscending(),(CreationSettings) mockSettings);//插樁時stubbing為nullif (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 #1279mockingProgress().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;@Overridepublic 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) {//這里就是把調用和返回綁定,如果下次調用匹配到了,就返回對應的answerif (isConsecutive) {stubbed.getFirst().addAnswer(answer);} else {//這里設置的answer并不是連續的,所以直接加到stubbed(LinkedList)的頭部就行Strictness effectiveStrictness = stubbingStrictness != null ? stubbingStrictness : this.mockStrictness;//這里的invocationForStubbingstubbed.addFirst(new StubbedInvocationMatcher(answer, invocationForStubbing, effectiveStrictness));}return stubbed.getFirst();}}這樣在單元測試調用Mock對象的方法時,會再次進入MockHandlerImpl的handle方法
public StubbedInvocationMatcher findAnswerFor(Invocation invocation) {synchronized (stubbed) {for (StubbedInvocationMatcher s : stubbed) {//匹配mock對象,方法信息,參數信息,如果都匹配上則說明是這個answerif (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 usedinvocation.markStubbed(new StubInfoImpl(s));return s;}}}return null;}@Overridepublic boolean matches(Invocation candidate) {return invocation.getMock().equals(candidate.getMock()) && hasSameMethod(candidate) && argumentsMatch(candidate);}最后執行StubbedInvocationMatcher的answer方法獲取最終的返回結果,如下:
public Object answer(InvocationOnMock invocation) throws Throwable {//see ThreadsShareGenerouslyStubbedMockTestAnswer 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);//拿到mockHandlerMockHandler 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主要的原理分析都完成了
總結
以上是生活随笔為你收集整理的单元测试源码分析之二Mockito自动装配和插桩的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 单元测试源码分析之一创建mock对象
- 下一篇: 注意System.currentTime