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,它内部配置的 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;
      }
    };
  }
  1. 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

更新时间:

留下评论