引言
在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 }
浙公网安备 33010602011771号