Mockito 详解(五)MockitoAnnotation

Posted by liangfei on 2017-07-26

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,它内部配置的 AnnotationEngineInjectingAnnotationEngine

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);
}
}
}
}
}
  1. 首先遍历所有的field,获取该field的annotations
  2. 然后根据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;
    }
    };
    }
  3. 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

欢迎扫码关注 老梁写代码 微信公众号