Mockito поддерживает аннотации с 0.9’ой версии. В той версии была введена аннотация @Mock
, которая позволяет создавать mock-объекты. Далее были добавлены и другие аннотации:
@Spy
создает spy-объекты@Captor
создает экземпляр ArgumentCapture@InjectMocks
внедряет mock- и spy- объекты в аннотированный объект@Incubating
определяет недавно добавленный класс, с возможностью его изменения.
Обработка аннотаций начинается с класса org.mockito.MockitoAnnotaions
. В период существования только одной анотации @Mock
, она была объявлена внутри этого класса, о чем свидетельствует deprecated аннотация. Позднее аннотации были вынесены в отдельные файлы.
Запуск обработки аннотаций осуществляется вызовом метода MockitoAnnotaions#initMocks
. Разработчики предоставили нам два пути по работе с этим классом:
- Вручную, передав в качестве параметра ссылку на объект теста:
MockitoAnnotations.initMocks(this)
- С помощью 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 |
|
А создаются эти реализации с помощью рефлексии по имени класса.
1 2 3 4 5 |
|
Необходимость такого решения на данный момент выясняется у разработчиков Mockito.
Реализации Mockito Runner в пакете org.mockito.internal.runners
:
JUnit44RunnerImpl
используетorg.junit.internal.runners.JUnit4ClassRunner
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 |
|
И есть непосредственно обработчик org.mockito.internal.listeners.CollectCreatedMocks
, реализующий этот интерфейс.
В Mockito есть центральный класс, который реализует интерфейс MockingProgress. Этот класс хранит состояние процесса тестирования с помощью Mockito. В момент установки состояния старта создания mock-объекта он оповещает обработчик соответствующего события следующим образом:
1 2 3 |
|
А если ещё принять во внимание, что объект MockingProgress
в большинстве случаев будет синглтоном, он принимает только один обработчик и метод setListener вынесен в его интерфейс, то становится страшно (надеюсь разработчики Mockito прокомментируют этот момент).