Mockito: Использование аннотаций

| Комментарии

Mockito поддерживает аннотации с 0.9’ой версии. В той версии была введена аннотация @Mock, которая позволяет создавать mock-объекты. Далее были добавлены и другие аннотации:

  • @Spy создает spy-объекты
  • @Captor создает экземпляр ArgumentCapture
  • @InjectMocks внедряет mock- и spy- объекты в аннотированный объект
  • @Incubating определяет недавно добавленный класс, с возможностью его изменения.

Обработка аннотаций начинается с класса org.mockito.MockitoAnnotaions. В период существования только одной анотации @Mock, она была объявлена внутри этого класса, о чем свидетельствует deprecated аннотация. Позднее аннотации были вынесены в отдельные файлы.

Запуск обработки аннотаций осуществляется вызовом метода MockitoAnnotaions#initMocks. Разработчики предоставили нам два пути по работе с этим классом:

  1. Вручную, передав в качестве параметра ссылку на объект теста: MockitoAnnotations.initMocks(this)
  2. С помощью junit runner’a: @RunWith(MockitoJUnitRunner.class)

Работа Runner’a организована следующим образом. Наружу из библиотеки выглядывает объект, реализующий интерфейс org.junit.runner.Runner для использования непосредственно в JUnit. В его конструкторе с помощью org.mockito.internal.runners.RunnerFactory создается экземпляр внутренней реализации Runner’a.

RunnerFactory инкапсулирует логику создания новых экземпляров Runner’ов в отдельный объект org.mockito.internal.runners.util.RunnerProvider. Такое решение основано на странном способе создания Runner’ов с помощью рефлексии. Об этом ниже.

Внутренняя реализация Runner’a (назовем его Mockito Runner) определяется версией JUnit, т.к. начиная с версии 4.5 основной реализацией JUnit Runner’a является класс org.junit.runners.BlockJUnit4ClassRunner. Соответственно, версия JUnit определяется наличием класса в classpath’е проекта:

1
2
3
4
5
6
try {
    Class.forName("org.junit.runners.BlockJUnit4ClassRunner");
    hasJUnit45OrHigher = true;
} catch (Throwable t) {
    hasJUnit45OrHigher = false;
}

А создаются эти реализации с помощью рефлексии по имени класса.

1
2
3
4
5
if (runnerProvider.isJUnit45OrHigherAvailable()) {
    return runnerProvider.newInstance("org.mockito.internal.runners.JUnit45AndHigherRunnerImpl", klass);
} else {
    return runnerProvider.newInstance("org.mockito.internal.runners.JUnit44RunnerImpl", klass);
}

Необходимость такого решения на данный момент выясняется у разработчиков Mockito.

Реализации Mockito Runner в пакете org.mockito.internal.runners:

  1. JUnit44RunnerImpl использует org.junit.internal.runners.JUnit4ClassRunner
  2. JUnit45AndHigherRunnerImpl использует org.junit.runners.BlockJUnit4ClassRunner

Mockito Runner в конструкторе создает анонимный класс, унаследованный от соответствующего JUnit Runner’a, где вызывается MockitoAnnotations#initMocks.

При использовании runner’ов для инициализации аннотаций Mockito автоматически добавляет свой org.junit.runner.notification.RunListener - FrameworkUsageValidator. Этот валидатор подписывается на событие testFinished и вызывает org.mockito.Mockito#validateMockitoUsage для самодиагностики.

В комплекте с MockitoJUnitRunner идет еще одна реализация Runner’a - ConsoleSpammingMockitoJUnitRunner. От первого она отличается тем, что добавляет свой RunListener, который по событию testFailure логирует все сообщения. На данный момент логируются события создания mock-объектов. За коллекционирование сообщений системы отвечает класс org.mockito.internal.debugging.WarningsCollector, который по глобальному событию создания mock’ов логирует что и как создалось.

Система событий в Mockito реализована своеобразно. Принцип работы отчасти позаимствован у JUnit, т.е. есть класс с методами событий. Переопределяя некоторые из них мы описываем обработчик соответствующего события. Но разработчики mockito пошли немного другим путем. Для каждого события создается отдельный интерфейс listener’a, который расширяет общий для всех listener’ов интерфейс MockingProgressListener. Самое интересное это то, что MockingProgressListener - это пустой интерфейс-маркер, а в интерфейс конкретного обработчика добавляются методы совершенно произвольной сигнатуры. На практике это выглядит следующим образом. Есть интерфейс обработчика события создания mock-объектов:

1
2
3
public interface MockingStartedListener extends MockingProgressListener {
    void mockingStarted(Object mock, Class classToMock);
}

И есть непосредственно обработчик org.mockito.internal.listeners.CollectCreatedMocks, реализующий этот интерфейс.

В Mockito есть центральный класс, который реализует интерфейс MockingProgress. Этот класс хранит состояние процесса тестирования с помощью Mockito. В момент установки состояния старта создания mock-объекта он оповещает обработчик соответствующего события следующим образом:

1
2
3
if (listener != null && listener instanceof MockingStartedListener) {
    ((MockingStartedListener) listener).mockingStarted(mock, classToMock);
}

А если ещё принять во внимание, что объект MockingProgress в большинстве случаев будет синглтоном, он принимает только один обработчик и метод setListener вынесен в его интерфейс, то становится страшно (надеюсь разработчики Mockito прокомментируют этот момент).

Comments