引言
在unit測試中有時也需要mock final類型和方法,Mockito作為一個使用者眾多的mock框架, 從2.1版本開始提供對mock final類型和方法的支持。
在本篇文章中我們通過兩部分來闡述和分析以下兩個問題:
1. 為什么不能在版本2.1之前支持 mock final類型和方法
2. 通過那些改進使得從版本2.1開始支持mock final類型和方法的,這些實現又有什么使用上的限制。
第一部分 為什么不能在版本2.1之前支持mock final類型和方法
要了解Mockito在早期版本中為什么不能支持final,首先要了解它是怎么創建mock對象的。Mockito采用動態代理(dynamic proxy)機制來創建代理對象,而這個動態代理是通過CGlib來實現的。CGlib的Wiki文檔是這樣描述它的:
cglib is a powerful, high performance and quality Code Generation Library, It is used to extend JAVA classes and implements interfaces at runtime.
CGlib是一種強大的,高效的和高質量的代碼生成器。它被用於在運行期擴展Java類和實現接口。
至於Mockito為什么沒有采用標准Java JDK Reflection來實現動態代理,原因在於標准JAVA 動態代理只能對實現了接口的原類來生成代理類,而cglib是通過子類化(subclassing)來創建代理類的,所創建的代理類是原類的動態子類。這種方法實際上是對JAVA JDK 標准動態代理的功能補充。 這就是說在Mockito中要使用Mock的測試類可以是一個沒有實現任何接口的基礎類也可以是實現了接口的類。例如假定被測試類A,A可以是
public class A implements B{} 或者 public class A {}
而對於JAVA JDK標准動態代理被測試類只可以是
public class A implements B{}
下面我們通過Mockito中的源代碼類ClassImposterizer.java來看看這個類的方法 createProxyClass 是怎樣使用CGLIB實現動態代理,進而再實現Mock的。通過代碼可以看到以下幾步被執行:
1. 一個Enhancer對象類的 對象被創建,這個類是CGlib中最常用的類,用來動態的創建子類來攔截方法。
2. 給enhancer對象的變量附值包括產生類的Classloader,factory 接口被實現賦值為TRUE , 被子類化的父類,也就是原類,如果原類實現了某個接口,則需要賦值 interface還有返回截取方法的Callback和callbackfilter還有生成Class的命名策略.
3.創造新類
注意源代碼的catch語句,在創造新類時有可能拋出MockitoException,出現Exception的原因代碼中給出如下信息:Mockito can only mock visible & non-final classes.這就是說從代碼來看,Mockito不能mock final類型和方法。
我們再從邏輯上思考,上面已經說過Enhancer是通過子類化動態產生代理類的,如果父類要mock一個 final方法,這個方法將不能通過子類化被重載。
結論:因為Mockito使用CGLIB來實現動態代理,而CGLIB是通過Subclassing重載要實現動態代理的父類的,因此final類型和方法都不能被mock.
1 public Class<Factory> createProxyClass(Class<?> mockedType, Class<?>... interfaces) { 2 if (mockedType == Object.class) { 3 mockedType = ClassWithSuperclassToWorkAroundCglibBug.class; 4 } 5 6 Enhancer enhancer = new Enhancer() { 7 @Override 8 @SuppressWarnings("unchecked") 9 protected void filterConstructors(Class sc, List constructors) { 10 // Don't filter 11 } 12 }; 13 Class<?>[] allMockedTypes = prepend(mockedType, interfaces); 14 enhancer.setClassLoader(SearchingClassLoader.combineLoadersOf(allMockedTypes)); 15 enhancer.setUseFactory(true); 16 if (mockedType.isInterface()) { 17 enhancer.setSuperclass(Object.class); 18 enhancer.setInterfaces(allMockedTypes); 19 } else { 20 enhancer.setSuperclass(mockedType); 21 enhancer.setInterfaces(interfaces); 22 } 23 enhancer.setCallbackTypes(new Class[]{MethodInterceptor.class, NoOp.class}); 24 enhancer.setCallbackFilter(IGNORE_BRIDGE_METHODS); 25 if (mockedType.getSigners() != null) { 26 enhancer.setNamingPolicy(NAMING_POLICY_THAT_ALLOWS_IMPOSTERISATION_OF_CLASSES_IN_SIGNED_PACKAGES); 27 } else { 28 enhancer.setNamingPolicy(MockitoNamingPolicy.INSTANCE); 29 } 30 31 enhancer.setSerialVersionUID(42L); 32 33 try { 34 return enhancer.createClass(); 35 } catch (CodeGenerationException e) { 36 if (Modifier.isPrivate(mockedType.getModifiers())) { 37 throw new MockitoException("\n" 38 + "Mockito cannot mock this class: " + mockedType 39 + ".\n" 40 + "Most likely it is a private class that is not visible by Mockito"); 41 } 42 throw new MockitoException("\n" 43 + "Mockito cannot mock this class: " + mockedType 44 + "\n" 45 + "Mockito can only mock visible & non-final classes." 46 + "\n" 47 + "If you're not sure why you're getting this error, please report to the mailing list.", e); 48 } 49 }