Вместе с mock’ом создается обработчик его событий. Это один обработчик на все методы мока. В итоге, мы имеем объект класса MockHandlerImpl
, который обернут wrapper’ами.
В момент вызова метода mock объекта вызывается MockHandlerImpl#handle
. Но этот метод вызывается не напрямую а из объекта MethodInterceptor
. Именно с этими обработчиками работает созданный CGLib библиотекой proxy объект. В Mockito есть собственная реализация MethodInterceptor
- MethodInterceptorFilter
, которая хранит внутри себя обработчик MockHandlerImpl
и настройки вызова. Итак, что же происходит в момент вызова метода mock объекта? Вызывается метод MethodInterceptorFilter#intercept
, куда передается ссылка на объект, в котором был вызван метод, ссылка на объект вызванного метода, массив переданных аргументов и объект MethodProxy
, который позволяет получить ссылку на реальный метод mock объекта. MethodInterceptorFilter
отделяет реализацию получения необходимых параметров вызова от самого обработчика. Далее все эти параметры помещаются в независимый от механизма создания proxy класс Invocation
. Этот объект передается методу обработчика MockHandlerImpl#handle
, который выполняет основную логику стаббированных методов.
Стоит понимать, что в Mockito есть как минимум 3 ситуации, когда вызов метода одного и того же mock объекта должен интерпретироваться по разному:
- в конструкции
when(mock.method(matcher))
- в конструкции
verify(mock).method(matcher)
- прямой вызов
mock.method(args...)
в тестируемом объекте
В обработчике(метод handle
) обрабатываются все эти ситуации. В одной из статей мы уже рассматривали объекты ответов - Answer
, которые определяются в конструкции when. В конструкции verify хранятся условия проверки. Плюс ко всему, есть matcher’ы, которые тоже накапливаются. Есть и другие параметры, которые должны быть переданы в обработчик. Странное решение, размывающее логику обработчика - это хранение этих артефактов в singleton объектах, доступ к которым имеют все компоненты системы одновременно. Основным таким объектом является MockingProgress. Этот объект мы уже рассматривали со стороны оповещения слушателей о событии создания mock’а. Он хранит в себе настройки verify блока и список matcher’ов. Плюс ко всему, т.к. эти объекты по большому счету нужны только в момент обработки stub’а, то нужно следить чтобы хранимые объекты не попали случайно в другой stub, поэтому Mockito хранит текущее состояние процесса стаббинга. Состояние определяется по наличию тех или иных объектов в реализации MockingProgress
. К примеру, если объект VerificationMode
не null, то объект в состоянии проверки. После извлечения хранимых объектов в них проставляется null, и объект готов к следующим процессам. Под процессом над стаббированным методом понимается один из трех процессов перечисленных в списке вызова обработчика метода mock’а выше в статье.
В каждом MockHandler’е создается InvocationContainer, который хранит в себе все ответы. В этот же контейнер переносятся все matcher’ы при их обработке. Этот же объект хранит информацию, которая проверяется в VerificationMode. Если задан VerificationMode, то выполняется проверка. Иначе вызывается метод инкрементирующий статистические данные о вызове методов.
Стоит заметить, что matcher’ы - объекты, которые позволяют задавать аргументы стаббируемых методов в виде выражений, реализуются на базе библиотеки Hamcrest. Насколько я знаю, многие тестирующие framework’и используют эту библиотеку. Mockito лишь расширяет её своими matcher’ами.
Далее вызывается поиск Answer объекта по объекту Invocation:
1
|
|
Где ищется соответствующий объект ответа для данного мока, для данного метода с аргумента, matcher которого соответствует переданным аргументам. Напомню, что вся эта информация хранится в объекте invocation с одной стороны и в объекте ответа(точнее в его обертке, которая используется во внутренней реализации) с другой.
Если ответ найден, то он вызывается с передачей ему объекта invocation. Иначе получаем ответ по-умолчанию и вызываем его:
1 2 3 4 5 6 7 8 |
|
Объект, возвращаемый ответом, и есть результат метода mock’а. Он и возвращается из функции.
Это последняя статья цикла Mockito. Мы увидели и хардкод и дублирование и, возможно, неудачные архитектурные решения. Чего стоит абстрагирование создания mock объектов, где базовые общие классы тесно переплетены с логикой библиотки CGLib. Замена CGLib альтернативами не будет легким занятием. Но вместе с тем Mockito является типичным примером тестирующего framework’а, который позволяет понять принципы работы подобных библиотек.
Если остались вопросы, то не стесняйтесь их задавать в комментариях, twitter’e/Google Plus’e или по почте.