Mockito 详解(五)MockitoAnnotation
MockitoAnnotations负责初始化@Mock
、@Spy
、@Captor
、@InjectMocks
等注解。
如果不用@Mock
,我们当然可以手动创建一个mock对象:
List mockedList = Mockito.mock(List.class);
但是相比于手动创建,使用注解可带来如下好处:
- 代码更简洁
- 避免重复创建
- 可读性好
- 验证错误更易读(因为注解默认使用field name来标记mock对象)
先来看一下用法:
public class ArticleManagerTest extends SampleBaseTestCase {
@Mock private ArticleCalculator calculator;
@Mock private ArticleDatabase database;
@Mock private UserProvider userProvider;
private ArticleManager manager;
@Before public void setup() {
manager = new ArticleManager(userProvider, database, calculator);
}
}
public class SampleBaseTestCase {
@Before public void initMocks() {
MockitoAnnotations.initMocks(this);
}
}
MockitoAnnotations
必须在执行测试方法之前(@Before
标记)执行初始化。
public class MockitoAnnotations {
public static void initMocks(Object testClass) {
if (testClass == null) {
throw new MockitoException("testClass cannot be null.");
}
AnnotationEngine annotationEngine = new GlobalConfiguration().tryGetPluginAnnotationEngine();
annotationEngine.process(testClass.getClass(), testClass);
}
}
所有注解都由AnnotationEngine
来处理。
寻找AnnotationEngine
为了保证线程安全,Mockito的configuration会保存在ThreadLocal,也就说一个线程只有一个实例。
public class GlobalConfiguration implements IMockitoConfiguration, Serializable {
private static final long serialVersionUID = -2860353062105505938L;
// 线程安全
private static final ThreadLocal<IMockitoConfiguration> GLOBAL_CONFIGURATION =
new ThreadLocal<IMockitoConfiguration>();
// 初始化
public GlobalConfiguration() {
if (GLOBAL_CONFIGURATION.get() == null) {
GLOBAL_CONFIGURATION.set(createConfig());
}
}
private IMockitoConfiguration createConfig() {
IMockitoConfiguration defaultConfiguration = new DefaultMockitoConfiguration();
IMockitoConfiguration config = new ClassPathLoader().loadConfiguration();
if (config != null) {
return config;
} else {
return defaultConfiguration;
}
}
public org.mockito.plugins.AnnotationEngine tryGetPluginAnnotationEngine() {
IMockitoConfiguration configuration = GLOBAL_CONFIGURATION.get();
if (configuration.getClass() == DefaultMockitoConfiguration.class) {
return Plugins.getAnnotationEngine();
}
return configuration.getAnnotationEngine();
}
}
IMockitoConfiguration
的实例存储在一个static ThreadLocal变量中——GLOBAL_CONFIGURATION
,所以在每一个线程中只有一个configuration实例,那么每次new GlobalConfiguration
并不会多次创建实例。
GlobalConfiguration
构造时会首先尝试通过ClassPathLoader
来加载configuration:
public class ClassPathLoader {
public static final String MOCKITO_CONFIGURATION_CLASS_NAME = "org.mockito.configuration.MockitoConfiguration";
public IMockitoConfiguration loadConfiguration() {
// try-catch is omitted.
Class<?> configClass = Class.forName(MOCKITO_CONFIGURATION_CLASS_NAME);
return (IMockitoConfiguration) configClass.newInstance();
}
}
MockitoConfiguration
是一个插件,关于插件的加载方式可参考Mockito 详解(二)插件机制。
如果加载不到类MockitoConfiguration
,说明没有配置插件,那么就退而求其次,使用默认值——DefaultMockitoConfiguration
,它内部配置的 AnnotationEngine
是InjectingAnnotationEngine
。
AnnotationEngine
找到了,开始分析如何处理annotation。
处理Annotation
AnnotationEngine
的接口规范如下:
public interface AnnotationEngine {
void process(Class<?> clazz, Object testInstance);
}
每个Annotation
所对应的AnnotationEngine
如下表所示:
Annotation | AnnotationEngine |
---|---|
@Mock & @Captor | IndependentAnnotationEngine |
@Spy | SpyAnnotationEngine |
@InjectMocks | InjectingAnnotationEngine |
因为注解会作用到单个变量(Field)上,根据注解初始化变量的工作由FieldAnnotationProcessor
完成:
public interface FieldAnnotationProcessor<A extends Annotation> {
Object process(A annotation, Field field);
}
process
的返回值Object
就是根据注解创建的对象。
IndependentAnnotationEngine(@Mock & @Captor)
@Mock
可以指定创建mock所需要的变量:
@Target({FIELD, PARAMETER})
@Retention(RUNTIME)
@Documented
public @interface Mock {
Answers answer() default Answers.RETURNS_DEFAULTS;
String name() default "";
Class<?>[] extraInterfaces() default {};
boolean serializable() default false;
}
MockAnnotationProcessor
会首先读取Mock
的参数,然后构建一个mockSettings
,最后通过调用Mockito#mock
创建一个mock对象。
public class MockAnnotationProcessor implements FieldAnnotationProcessor<Mock> {
public Object process(Mock annotation, Field field) {
MockSettings mockSettings = Mockito.withSettings();
if (annotation.extraInterfaces().length > 0) { // never null
mockSettings.extraInterfaces(annotation.extraInterfaces());
}
// 默认使用field name
if ("".equals(annotation.name())) {
mockSettings.name(field.getName());
} else {
mockSettings.name(annotation.name());
}
if (annotation.serializable()) {
mockSettings.serializable();
}
mockSettings.defaultAnswer(annotation.answer());
return Mockito.mock(field.getType(), mockSettings);
}
}
Captor
的原理是一样的,它会创建一个ArgumentCaptor
。
public class CaptorAnnotationProcessor implements FieldAnnotationProcessor<Captor> {
public Object process(Captor annotation, Field field) {
Class<?> type = field.getType();
if (!ArgumentCaptor.class.isAssignableFrom(type)) {
// exception message is omitted
throw new MockitoException("");
}
Class<?> cls = new GenericMaster().getGenericType(field);
return ArgumentCaptor.forClass(cls);
}
}
通过代码可以看出,@Captor
标记的变量必须是ArgumentCaptor
类型。
IndependentAnnotationEngine
会初始化一个Annotation Class到FieldAnnotationProcessor的映射:
// 成员变量,省略了new
private final Map<Class<? extends Annotation>, FieldAnnotationProcessor<?>> annotationProcessorMap;
// 构造方法,注册了两个annotation processor
public IndependentAnnotationEngine() {
registerAnnotationProcessor(Mock.class, new MockAnnotationProcessor());
registerAnnotationProcessor(Captor.class, new CaptorAnnotationProcessor());
}
MockitoAnnotations#initMocks
方法直接调用了AnnotationEngine#process
:
annotationEngine.process(testClass.getClass(), testClass);
IndependentAnnotationEngine#process
的实现如下所示:
public void process(Class<?> clazz, Object testInstance) {
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
boolean alreadyAssigned = false;
for(Annotation annotation : field.getAnnotations()) {
Object mock = createMockFor(annotation, field);
if (mock != null) {
throwIfAlreadyAssigned(field, alreadyAssigned);
alreadyAssigned = true;
try {
setField(testInstance, field,mock);
} catch (Exception e) {
throw new MockitoException("Problems setting field " + field.getName() + " annotated with "
+ annotation, e);
}
}
}
}
}
- 首先遍历所有的
field
,获取该field
的annotations - 然后根据annotation类型创建mock对象
private Object createMockFor(Annotation annotation, Field field) {
return forAnnotation(annotation).process(annotation, field);
}
private <A extends Annotation> FieldAnnotationProcessor<A> forAnnotation(A annotation) {
if (annotationProcessorMap.containsKey(annotation.annotationType())) {
return (FieldAnnotationProcessor<A>) annotationProcessorMap.get(annotation.annotationType());
}
return new FieldAnnotationProcessor<A>() {
public Object process(A annotation, Field field) {
return null;
}
};
}
setField
会把新创建的mock对象——Object mock
通过反射赋值给testInstance
的成员变量。
public class FieldSetter {
private FieldSetter() {
}
public static void setField(Object target, Field field, Object value) {
AccessibilityChanger changer = new AccessibilityChanger();
changer.enableAccess(field);
try {
field.set(target, value);
} catch (IllegalAccessException e) {
throw new RuntimeException("msg omitted");
} catch (IllegalArgumentException e) {
throw new RuntimeException("msg omitted");
}
changer.safelyDisableAccess(field);
}
}
总结
MockitoAnnotations
只是负责初始化testInstance
内用Annotation
标记的Field
。Field
通过Mockito#mock
完成初始化。MockitoSession
除了借助MockitoAnnotations
完成Field
初始化之外,还会监控整个mock progress
留下评论