Как работает вызов mockito when ()?


111

Учитывая следующее заявление Mockito:

when(mock.method()).thenReturn(someValue);

Как Mockito создает проксирование чего-либо для имитации, учитывая, что оператор mock.method () передает возвращаемое значение в when ()? Я предполагаю, что здесь используются некоторые вещи CGLib, но мне было бы интересно узнать, как это делается технически.

Ответы:


118

Короткий ответ заключается в том, что в вашем примере результатом mock.method()будет соответствующее типу пустое значение; mockito использует косвенное обращение через прокси, перехват метода и общий экземпляр MockingProgressкласса, чтобы определить, предназначен ли вызов метода в макете для заглушки или воспроизведения существующего заглушенного поведения, а не для передачи информации о заглушке через возвращаемое значение издевательский метод.

Ниже представлен мини-анализ кода mockito за пару минут. Обратите внимание: это очень приблизительное описание - здесь задействовано много деталей. Предлагаю вам самому проверить исходники на github .

Во-первых, когда вы имитируете класс с помощью mockметода Mockitoкласса, по сути происходит следующее:

  1. Mockito.mockделегирует org.mockito.internal.MockitoCore.mock, передавая имитационные настройки по умолчанию в качестве параметра.
  2. MockitoCore.mockделегирует org.mockito.internal.util.MockUtil.createMock
  3. MockUtilКласс использует ClassPathLoaderкласс , чтобы получить экземпляр MockMakerиспользовать для создания издеваться. По умолчанию используется класс CgLibMockMaker .
  4. CgLibMockMakerиспользует класс, заимствованный из JMock, ClassImposterizerкоторый обрабатывает создание макета. Ключевые части используемой «магии mockito» используются MethodInterceptorдля создания mock: mockito MethodInterceptorFilterи цепочка экземпляров MockHandler, включая экземпляр MockHandlerImpl . Перехватчик метода передает вызовы экземпляру MockHandlerImpl, который реализует бизнес-логику, которая должна применяться при вызове метода в макете (т. Е. Поиск, чтобы увидеть, записан ли уже ответ, определение того, представляет ли вызов новую заглушку и т. Д. Состояние по умолчанию: если заглушка еще не зарегистрирована для вызываемого метода, возвращается соответствующее типу пустое значение.

Теперь давайте посмотрим на код в вашем примере:

when(mock.method()).thenReturn(someValue)

Вот порядок, в котором будет выполняться этот код:

  1. mock.method()
  2. when(<result of step 1>)
  3. <result of step 2>.thenReturn

Ключ к пониманию того, что происходит, - это то, что происходит при вызове метода в макете: перехватчику метода передается информация о вызове метода и он делегирует свою цепочку MockHandlerэкземпляров, которые в конечном итоге делегируют MockHandlerImpl#handle. Во MockHandlerImpl#handleвремя обработки фиктивный обработчик создает экземпляр OngoingStubbingImplи передает его общему MockingProgressэкземпляру.

Когда whenметод вызывается после вызова method(), он делегирует MockitoCore.when, который вызывает stub()метод того же класса. Этот метод распаковывает текущую заглушку из общего MockingProgressэкземпляра, в который записан method()фиктивный вызов, и возвращает его. Затем thenReturnдля OngoingStubbingэкземпляра вызывается метод .


1
Спасибо за подробный ответ. Другой вопрос - вы упоминаете, что «когда метод вызывается после вызова метода ()» - как он узнает, что вызов when () является следующим (или завершает) вызов метода ()? Надеюсь, это имеет смысл.
marchaos

@marchaos Не знает. С when(mock.method()).thenXyz(...)синтаксисом mock.method()выполняется в режиме «воспроизведения», а не в режиме «заглушки». Как правило, это исполнение mock.method()не имеет никакого эффекта, поэтому позже , когда thenXyz(...)( thenReturn, thenThrow, thenAnswerи т.д.) запускается на выполнение, он переходит в «гася» режим , а затем записывает желаемый результат для этого вызова метода.
Rogério

1
Роджерио, на самом деле это немного более тонко, чем это - mockito не имеет явных режимов заглушки и воспроизведения. Я отредактирую свой ответ позже, чтобы он был более понятным.
Пол Мори

Короче говоря, проще перехватить вызов метода внутри другого метода с помощью CGLIB или Javassist, чем перехватить, скажем, оператор «если».
Infeligo

Я еще не массировал здесь свое описание, но и об этом не забыл. FYI.
Пол Мори

33

Короткий ответ: за кулисами Mockito использует какие-то глобальные переменные / хранилище для сохранения информации об этапах построения заглушки метода (вызов метода (), when (), thenReturn () в вашем примере), чтобы в конечном итоге он мог создать карту того, что должно быть возвращено, когда то, что вызывается для какого параметра.

Я нашел эту статью очень полезной: Объяснение того, как работают Mock Framework на основе прокси ( http://blog.rseiler.at/2014/06/explanation-how-proxy-based-mock.html ). Автор реализовал демонстрационный фреймворк Mocking, который я нашел очень хорошим ресурсом для людей, которые хотят понять, как работают эти фреймворки Mocking.

На мой взгляд, это типичное использование Anti-Pattern. Обычно при реализации метода следует избегать «побочного эффекта», то есть метод должен принимать ввод, производить некоторые вычисления и возвращать результат - кроме этого ничего не изменилось. Но Mockito просто намеренно нарушает это правило. Его методы хранят кучу информации, помимо возврата результата: Mockito.anyString (), mockInstance.method (), when (), thenReturn, все они имеют специальный «побочный эффект». Вот почему на первый взгляд фреймворк выглядит как волшебство - мы обычно не пишем такой код. Однако в случае фреймворка mocking этот антипаттерн - отличный дизайн, поскольку он приводит к очень простому API.


4
Отличная ссылка. Гениальность этого заключается в следующем: очень простой API, благодаря которому все выглядит очень красиво. Еще одно отличное решение заключается в том, что метод when () использует универсальные шаблоны, поэтому метод thenReturn () является типобезопасным.
Дэвид Тонхофер 07

Я считаю это лучшим ответом. В отличие от другого ответа, он четко объясняет концепции процесса имитации, а не потока управления через конкретный код. Согласен, отличная ссылка.
mihca
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.