Injectmocks как работает
Перейти к содержимому

Injectmocks как работает

  • автор:

Mockito: @InjectMocks

Процесс внедрения mock’ов разделен на несколько этапов:

  1. Поиск полей, отмеченных аннотацией @InjectMocks
  2. Поиск мок объектов в тесте
  3. Внедрение найденных моков в поля, помеченные аннотацией @InjectMocks
1 2 3 4 5 6 7 8 
Set mockDependentFields = new HashSet(); Set mocks = newMockSafeHashSet(); while (clazz != Object.class)  new InjectMocksScanner(clazz).addTo(mockDependentFields); new MockScanner(testClassInstance, clazz).addPreparedMocks(mocks); clazz = clazz.getSuperclass(); >

В Mockito используется собственный утилитарный класс Sets , с помощью которого создаются внутренние реализации множеств. В данном случае заимпортирован один из его статичных методов newMockSafeHashSet. Метод создает реализацию под названием org.mockito.internal.util.collections.HashCodeAndEqualsSafeSet . Создана отдельная реализация Set интерфейса лишь для того, чтобы скрыть особенность реализации моков. А именно, есть некоторая проблема в стаббинге методов Object.equals и Object.hashCode , и они могут выкидывать NullPointerException . Истинную причину выясним при разборе процесса subbing’а. При работе в коллекциях эти методы интенсивно используются при поиске и добавлении элементов, поэтому мы оборачиваем мок объект в этот wrapper, который использует свои методы equals и hashCode . Оборачивание моков происходит в момент добавления элементов в множество.

Вернемся к примеру. В множество mocks у нас будут добавлены все найденные mock-объекты. Стоит заметить, что в Mockito много переменных модифицируются, будучи переданными через аргументы. Так и в данном случае коллекции наполяются именно так. Immutability здесь явно не хватает. org.mockito.internal.configuration.injection.scanner.InjectMocksScanner реализует алгоритм поиска полей, отмеченных аннотацией @InjectMocks . Основные два метода класса:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 
private Set scan()  Set mockDependentFields = new HashSet(); Field[] fields = clazz.getDeclaredFields(); for (Field field : fields)  if (null != field.getAnnotation(InjectMocks.class))  assertNoAnnotations(field, Mock.class, MockitoAnnotations.Mock.class, Captor.class); mockDependentFields.add(field); > > return mockDependentFields; > void assertNoAnnotations(final Field field, final Class. annotations)  for (Class annotation : annotations)  if (field.isAnnotationPresent(annotation))  new Reporter().unsupportedCombinationOfAnnotations(annotation.getSimpleName(), InjectMocks.class.getSimpleName()); > > >

Именно результат метода scan и добавляет в наше множество. В цикле пробегаем по полям класса-теста, проверяем есть ли у поля аннотация @InjectMocks . Стоит отметить, что используются системные java классы полей и классов. Пока, по-моему, ничего сверхъестественного. Далее, если метод аннотирован, то проверяем не аннотирован ли он еще и стаббирующими аннотациями ( @Mock , @Captor ), т.к. это противоречит логике и не может быть допущено. В случае нарушения правил выбрасывается RuntimeException . Если все в порядке, то мы можем добавить поле в наше множество.

org.mockito.internal.configuration.injection.scanner.MockScanner реализуется второй этап алгоритма внедрения моков — поиск мок объектов в тесте. Основные методы класса:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 
private Set scan()  Set mocks = newMockSafeHashSet(); for (Field field : clazz.getDeclaredFields())  // mock or spies only FieldReader fieldReader = new FieldReader(instance, field); Object mockInstance = preparedMock(fieldReader.read(), field); if (mockInstance != null)  mocks.add(mockInstance); > > return mocks; > private Object preparedMock(Object instance, Field field)  if (isAnnotatedByMockOrSpy(field))  return instance; > else if (isMockOrSpy(instance))  mockUtil.maybeRedefineMockName(instance, field.getName()); return instance; > return null; > private boolean isAnnotatedByMockOrSpy(Field field)  return null != field.getAnnotation(Spy.class) || null != field.getAnnotation(Mock.class) || null != field.getAnnotation(MockitoAnnotations.Mock.class); > private boolean isMockOrSpy(Object instance)  return mockUtil.isMock(instance) || mockUtil.isSpy(instance); >

Класс выполнен в стиле InjectMocksScanner , хотя общего интерфейса у классов нет. Но это не страшно, т.к. выполняют они сугубо утилитарные функции. Результат метода scan помещается в множество найденных мок объектов для внедрения. За чтение значений полей класса из конкретных объектов отвечает FieldReader. Основная строчка его метода read:

return field.get(target)

В методе MockScanner#preparedMock определяется, является ли полученный из поля объект mock’ом. Определяется он по наличию в поле аннотаций @Mock или @Spy . Если аннотаций нет, то есть вероятность, что объекты были стаббированы вручную. Это проверяется в методе MockScanner#isMockOrSpy . В случае, если объект все-таки является Mock объектом, то возможно требуется его переименование. Необходимость такого решения возможно выяснится позднее, когда мы разберем процесс создания Mock объектов. На данный момент это выглядит каким то костылем.

Осталось дело за малым — внедрить мок-объекты. Отправная точка org.mockito.internal.configuration#DefaultInjectionEngine — объект, который инициализирует обработчиков-внедренцев. И запускает процесс инжектирования. Внедрением занимаются org.mockito.internal.configuration.injection.MockInjectionStrategy объекты. На данный момент моки инжектируются через конструктор ( ConstructorInjection ), поля класса и сеттеры ( PropertyAndSetterInjection ).

Поле, помеченное аннотацией @InjectMocks, может быть помечено ещё и аннотацией @Spy, что заставляет, в случае неуспешного внедрения моков в объект, создавать из него Spy-объект. Хотя в целом JavaDoc полный, этот факт скрыт от глаз пользователей. 

Для обработки @Spy аннотации, если не получилось создать объект, в который внедрялись моки, используется SpyOnInjectedFieldsHandler . Основной метод DefaultInjectionEngine — #injectMocksOnFields :

1 2 3 4 5 6 7 8 
public void injectMocksOnFields(Set needingInjection, Set mocks, Object testClassInstance)  MockInjection.onFields(needingInjection, testClassInstance) .withMocks(mocks) .tryConstructorInjection() .tryPropertyOrFieldInjection() .handleSpyAnnotation() .apply(); >

Как видно, объект-конфигуратор процесса внедрения — org.mockito.internal.configuration.injection.MockInjection . Он определяет внутри себя реализации той или иной стратегии. Поэтому его методы tryConstructorInjection , tryPropertyOrFieldInjection и handleSpyAnnotation возвращают именно те классы стратегий, которые мы рассмотрели выше. Метод onFields возвращает объект OngoingMockInjection — внутренний класс класса MockInjection . Его тип нас не должен интересовать, т.к. проектировался этот объект для использования здесь и сейчас в виде Builder’а, что обеспечивается возвращением из каждого его метода ссылки на самого себя. Этот объект-builder сохраняет в конструкторе поля с аннотацией @InjectMocks . Далее метод withMocks сохраняет список mock объектов. Странный подход к инициализации базовых данных. Практичнее было бы внести сохранение списка mock объектов в конструктор, т.к. инжектирование без них смысла не имеет. А так как порядок выполнения Builder методов не регулируется, то можно пропустить этот метод. Далее 3’мя следующими методами объявляем стратегии инжектирования, которые хотим использовать. Стратегии инжектирования образуют цепочку стратегий по примеру паттерна Composite. Вызывая основной метод первого из них, он обрабатывает основную логику, затем вызывается аналогичный метод связанного с ним объекта-внедренца. И так до последнего. На практике это реализуется следующим образом:

private MockInjectionStrategy injectionStrategies = MockInjectionStrategy.nop();

В OngoingMockInjection создается поле с пустой стратегией обработчиком, где nop() — фабричный метод возвращающий пустую реализацию:

1 2 3 4 5 6 7 
public static final MockInjectionStrategy nop()  return new MockInjectionStrategy()  protected boolean processInjection(Field field, Object fieldOwner, Set mockCandidates)  return false; > >; >

processInjection — основной абстрактный метод MockInjectionStrategy, в котором определяется логика внедрения моков. Методы добавления следующего в цепочке обработчика выглядят следующим образом:

1 2 3 4 5 6 7 8 9 
public OngoingMockInjection tryConstructorInjection()  injectionStrategies.thenTry(new ConstructorInjection()); return this; > public OngoingMockInjection tryPropertyOrFieldInjection()  injectionStrategies.thenTry(new PropertyAndSetterInjection()); return this; >

Где метод thenTry передает добавляемый объект следующему в цепочке объекту, пока не будет достигнут последний объект в цепочке. Он то и примет к себе новое звено:

1 2 3 4 5 6 7 8 
public MockInjectionStrategy thenTry(MockInjectionStrategy strategy)  if(nextStrategy != null)  nextStrategy.thenTry(strategy); > else  nextStrategy = strategy; > return strategy; >

Метод OngoingMockInjection#apply , который завершает цепочку методов Builder’а, выполняет метод injectionStrategies.process, первого из цепочки стратегий обработки объекта для каждого поля, аннотированного @InjectMocks :

1 2 3 4 5 6 
public boolean process(Field onField, Object fieldOwnedBy, Set mockCandidates)  if(processInjection(onField, fieldOwnedBy, mockCandidates))  return true; > return relayProcessToNextStrategy(onField, fieldOwnedBy, mockCandidates); >

Где осуществляется вызов метода с логикой обработки, а затем в методе relayProcessToNextStrategy передается управление следующему объекту в цепи.

Метод OngoingMockInjection#handleSpyAnnotation прикрепляет финальный обработчик @Spy аннотаций в другой MockInjectionStrategy#nop() , который также вызывается для каждого поля в методе apply после вызова цепочки стратегий внедрения.

Рассмотрим алгоритм внедрения через конструктор:

ConstructorInjection определяет внутри себя реализацию ConstructorArgumentResolver’а, где определяется алгоритм получения mock’ов из найденного заранее множества, которые могут быть приравнены к типам аргументов конструктора.

1 2 3 4 5 6 7 
public Object[] resolveTypeInstances(Class. argTypes)  List argumentInstances = new ArrayList(argTypes.length); for (Class argType : argTypes)  argumentInstances.add(objectThatIsAssignableFrom(argType)); > return argumentInstances.toArray(); >

Где метод objectThatIsAssignableFrom перебирает множество mock объектов и ищет первое из них, которое удовлетворяет условию:

argType.isAssignableFrom(mock.getClass())

Выбор конструктора, определение его параметров и создание нового экземпляра класса для поля, аннотированного @InjectMocks , осуществляется в классе org.mockito.internal.util.reflection.FieldInitializer . Внутри определены две реализации ConstructorInstantiator для обработки — конструктор с параметрами и без параметров. В ConstructorInjection используется ParameterizedConstructorInstantiator , так как внедрение mock объектов в конструктор без параметров смысла не имеет. Соответственно, первое что делается — это проверка — не создан ли объект в целевом поле.

1 2 3 4 5 6 7 8 
private FieldInitializationReport acquireFieldInstance() throws IllegalAccessException  Object fieldInstance = field.get(fieldOwner); if(fieldInstance != null)  return new FieldInitializationReport(fieldInstance, false, false); > return instantiator.instantiate(); >

Значение объекта получается уже знакомым способом через метод get. Если объект уже в поле создан, то нет смысла идти дальше — возвращаем результат о неуспешном создании объекта. FieldInitializationReport — класс с данными результатов создания класса. instantiator — это и есть один из экземпляров ConstructorInstantiator . По методу instantiate выполняется основная логика поиска соответствующего конструктора и создания самого объекта. Основное содержание метода:

1 2 3 4 5 6 7 8 
constructor = biggestConstructor(field.getType()); changer.enableAccess(constructor); final Object[] args = argResolver.resolveTypeInstances(constructor.getParameterTypes()); Object newFieldInstance = constructor.newInstance(args); new FieldSetter(testClass, field).set(newFieldInstance); return new FieldInitializationReport(field.get(testClass), false, true);

С помощью метода biggestConstructor (кстати, неудачное название, т.к. не отражает действия) определяется конструктор с наибольшим количеством параметров. Реализация тривиальна.

С помощью метода changer.enableAccess изменяем доступность конструктора:

constructor.setAccessible(true);

При этом сохраняем старое значение accesible, чтобы вернуть его в конце метода в блоке finally:

1 2 3 4 5 
finally  if(constructor != null)  changer.safelyDisableAccess(constructor); > >

argResolver — реализация ConstructorArgumentResolver , которая определяется внутри ConstructorInjection (о нем мы говорили выше). С помощью него мы получаем массив mock объектов в нужном нам порядке и создаем с помощью них новый экземпляр класса целевого поля. FieldSetter — противоположность FieldReader’а . Вставляет значение в поле, при этом также меняя видимость поля. В конце возвращается FieldInitializationReport с отчетом об успешном создании объекта. В дальнейшем из этого объекта получится флаг fieldWasInitializedUsingContructorArgs ,и если он равен true , то вызов следующих обработчиков в цепочке прекращается.

Остальные обработчики работают по такому же принципу. Так что нет особого смысла их детально рассматривать.

На этом рассмотрение аннотаций заканчивается. В комментариях отвечу на любые вопросы.

Posted by Code insiders 00:33 29.06.2012 Mockito

Mockito и как его готовить

Перед вами очередное руководство по Mockito. В нём я, с одной стороны, попытался описать функционал этой библиотеки так, чтобы незнакомый с нею читатель сразу получил возможность полноценно ею пользоваться, а не только общее представление о ней. С другой — я хотел сделать его достаточно компактным и структурированным, чтобы можно было быстро прочесть его целиком и быстро найти в нём что-то единожды прочитанное, но подзабытое. В общем, эта такая статья, какая бы пригодилась мне самому, когда я только столкнулся с этой библиотекой и не очень понимал, как она работает.

Полагаю, она и сейчас может пригодиться мне же — иногда я забываю что-то из этого, а вспоминать материал удобнее всего не по официальной документации или чужим статьям, а по собственному, скажем так, конспекту. Вместе с тем я старался построить текст так, чтобы он был удобен прежде всего для знакомства с Mockito с нуля, и кое-где подробно разбираю вроде бы очевидные вещи — не все из которых были для меня очевидными с самого начала.

Mockito: что это такое и зачем нужно

Говоря коротко, Mockito — фреймворк для работы с заглушками.

Как известно, при тестировании кода (прежде всего юнит-тестировании, но не только) тестируемому элементу часто требуется предоставить экземпляры классов, которыми он должен пользоваться при работе. При этом часто они не должны быть полнофункциональными — наоборот, от них требуется вести себя жёстко заданным образом, так, чтобы их поведение было простым и полностью предсказуемым. Они и называются заглушками (stub). Чтобы их получить, можно создавать альтернативные тестовые реализации интерфейсов, наследовать нужные классы с переопределением функционала и так далее, но всё это достаточно неудобно, избыточно и чревато ошибками. Более удобное во всех смыслах решение — специализированные фреймворки для создания заглушек. Одним из таковых (и, пожалуй, самым известным для Java) и является Mockito.

Mockito позволяет создать одной строчкой кода так называемый mock (что-то вроде основы для нужной заглушки) любого класса. Для такого mock сразу после создания характерно некое поведение по умолчанию (все методы возвращают заранее известные значения — обычно это null либо 0 ). Можно переопределить это поведение желаемым образом, проконтролировать с нужной степенью детальности обращения к ним так далее. В результате mock и становится заглушкой с требуемыми свойствами. Ниже я подробно разберу, как это сделать.

Отмечу, что mock можно создать и для тех классов, новый экземпляр которых вообще-то так просто не создашь, в частности, классов с исключительно приватными конструкторами типа синглтонов и утилитных классов, а при минимальной настройке фреймворка — и перечислений (enums).

Окружение, версии и подопытное животное

При написании этой статьи я использовал:

  • Mockito: ‘org.mockito:mockito-core:2.24.0’ (последняя стабильная версия на момент написания)
  • TestNG: ‘org.testng:testng:6.14.3’ в качестве тестового фреймворка
  • AssertJ: ‘org.assertj:assertj-core:3.11.1’ в качестве инструмента проверок
  • Lombok: ‘org.projectlombok:lombok:1.18.6’ (просто для удобства)
  • Java 8

Для своих бесчеловечных экспериментов я написал вот такой интерфейс сервиса, предоставляющего доступ к неким данным.

public interface DataService < void saveData(ListdataToSave); String getDataById(String id); String getDataById(String id, Supplier calculateIfAbsent); List getData(); List getDataListByIds(List idList); List getDataByRequest(DataSearchRequest request); >

А это (пусть уж будет для порядка) код класса запроса, передаваемого в последний из методов интерфейса.

@AllArgsConstructor @Getter class DataSearchRequest

Единицы данных идентифицируются по ID и имеют ещё некоторые характеристики, но непосредственно в том виде, в котором возвращаются сервисом, они представляют собой строки, а не какие-то более сложные объекты. Ничего важного я так не упускаю, а примеры получаются проще и нагляднее.

Сразу же отмечу: в примерах ниже я для наглядности непосредственно вызываю переопределённые методы моих mock-объектов, но при реальном тестировании идея вовсе не в этом! В настоящем тесте я бы последовательно выполнил следующее:

  • настроил mock моего сервиса нужным образом;
  • передал его (вероятнее всего, через конструктор) экземпляру использующего его другого класса (предположим, содержащего какую-то бизнес-логику, использующую предоставляемые DataService данные), который я, собственно, и тестировал бы;
  • задействовал функционал тестируемого класса и проконтролировал бы результаты;
  • при необходимости проконтролировал бы количество и порядок вызовов метода(ов) моего mock, которые должны были быть вызваны тестируемым классом в результате предыдущего действия.

mock и spy

Центральный класс Mockito, через который предполагается обращаться к большей части функционала, — это, собственно, класс под названием Mockito (есть также класс BDDMockito , предоставляющий примерно те же возможности в форме, более подходящей для BDD, но здесь я не стану на нём останавливаться). Доступ к функционалу реализован через его статические методы.

Чтобы создать mock класса DataService , я должен сделать всего лишь следующее:

DataService dataServiceMock = Mockito.mock(DataService.class);

Готово — я получил экземпляр нужного мне класса. Он будет принят любым методом или конструктором, которому требуется параметр такого типа (например, конструктором того класса, который я хочу протестировать). Даже если далее его ожидает проверка с пристрастием, он её пройдёт: не только instanceof DataService вернёт true , но и dataServiceMock.getClass() — именно DataService.class . Каким-то формальным образом программно отличить mock-объект от обычного оказывается довольно непростой задачей, что и логично: ведь первый предназначен как раз для того, чтобы быть неотличимым от второго. Однако в составе Mockito для этого есть инструмент — метод Mockito.mockingDetails . Передав ему произвольный объект, я получу объект класса MockingDetails . Он содержит информацию о том, что этот объект представляет собой с точки зрения Mockito: является ли он mock, spy (см. ниже), как использовался, как был создан и прочее.

Особо нужно упомянуть ситуацию, когда я пытаюсь создать mock для final класса или mock-экземпляр enum либо переопределить поведение final метода. В таком случае при поведении Mockito по умолчанию код выше откажется работать, сославшись именно на это обстоятельство. Однако это можно изменить — достаточно создать в проекте (при стандартном устройстве проектного дерева каталогов) файл test/resources/mockito-extensions/org.mockito.plugins.MockMaker и вписать в него строчку:

mock-maker-inline

После этого можно имитировать обычным способом final классы и enum’ы, а также переопределять final методы.

Полученный мной mock в действии максимально безлик: ни один метод при вызове не окажет никакого воздействия на что бы то ни было, а возвращённое значение окажется null для объектных типов и 0 для примитивных. Обратите внимание: если метод возвращает коллекцию, mock’ом по умолчанию будут возвращены не null ‘ы, а пустые экземпляры коллекций. Например, для List это окажется пустой LinkedList независимо от того, что должен был возвращать реальный метод. А вот в качестве значений массивов, примитивных или объектных, я получу null . Поведение по умолчанию (и не только его) можно изменить при помощи функционала класса MockSettings , но это нечасто бывает нужно.

Так или иначе, в большинстве случаев мне понадобится не поведение по умолчанию, и в следующем разделе я подробно разберу, как задавать вместо него то, которое требуется.

Однако что, если я хочу использовать в качестве заглушки объект реального класса с имеющимся функционалом, переопределив работу только части его методов? Если речь о юнит-тестировании, такая потребность обычно (но не всегда) свидетельствует о том, что в проекте не всё в порядке с дизайном, и в принципе так действовать не рекомендуется. Однако случаются ситуации, когда этого по какой-то причине не избежать. На этот случай в Mockito есть так называемые spy, «шпионы». В отличие от mock’ов, их можно создавать на основе как класса, так и готового объекта:

DataService dataServiceSpy = Mockito.spy(DataService.class); // or DataService dataService = new DataService(); dataServiceSpy = Mockito.spy(dataService);

При создании spy на основе класса, если его тип — интерфейс, будет создан обычный mock-объект, а если тип — класс, то Mockito попытается создать экземпляр при помощи конструктора по умолчанию (без параметров). И только если такого конструктора нет, произойдёт ошибка и тест не сработает.

Поведение spy-объектов по умолчанию идентично поведению обычного экземпляра класса, однако они дают мне те же возможности, что и mock-объекты: позволяют переопределять их поведение и наблюдать за их использованием (см. следующие разделы). Важный момент: spy — не обёртка вокруг того экземпляра, на основе которого он создан! Поэтому вызов метода spy на состояние изначального экземпляра не повлияет.

Управление поведением

Итак, о том, как заставить mock или spy делать то, что мне нужно. Далее я буду везде писать просто «mock» — это будет значить «mock или spy», кроме случаев, где прямо сказано иное.

В целом управление поведением mock-объекта сводится к одной очевидной концепции: когда на mock так-то воздействовали (то есть вызван такой-то метод с такими-то аргументами), он должен отреагировать так-то и так-то. У этой концепции существуют две реализации в рамках класса Mockito — основная, рекомендуемая разработчиками к использованию везде, где это возможно, и альтернативная, применяемая там, где основная не годится.

Основная реализация базируется на методе Mockito.when . Этот метод принимает в качестве «параметра» вызов переопределяемого метода mock-объекта (таким образом фиксируется определяемое воздействие) и возвращает объект типа OngoingStubbing , позволяющий вызвать один из методов семейства .then. (так задаётся реакция на это воздействие). Всё вместе в простейшем случае выглядит примерно так:

List data = new ArrayList<>(); data.add("dataItem"); Mockito.when(dataService.getAllData()).thenReturn(data);

После этой операции я, вызвав у объекта dataService метод getAllData() , получу объект, заданный в первой строчке листинга.

Здесь привычная «объектно-ориентированная» интуиция может дать некоторый сбой, так что на этом стоит остановиться чуть подробнее. С точки зрения синтаксиса Java значением, передаваемым методу when в качестве параметра, является, разумеется, значение, возвращаемое переопределяемым методом. Для mock это пустое значение, для spy — значение, возвращаемое методом реального объекта. Но благодаря действующей «под капотом» Mockito магии метод when сработает штатным образом (а не упадёт при запуске с ошибкой) лишь в том случае, если внутри скобок после when находится именно вызов метода mock-объекта.

Подобная идеология часто действует при задании поведения mock в Mockito: вызывая метод (mock-объекта или класса Mockito ), я стремлюсь не получить возвращаемое им значение, а каким-то образом повлиять на возможный вызов того метода mock-объекта, с которым работаю: указать его границы, задать результат, установить наблюдение за его вызовами и так далее. Звучит несколько туманно, признаю, и при первом столкновении выглядит странно, но, разобравшись, очень скоро начинаешь ощущать этот подход как совершенно естественный в контексте работы с заглушками.

Альтернативная реализация связывания условия и результата вызова — методы семейства Mockito.do. . Эти методы позволяют задать поведение начиная с результата вызова и возвращают объект класса Stubber , уже при помощи которого можно задать условие. То же самое связывание, что и выше, выполненное этим способом, выглядит так:

List data = new ArrayList<>(); data.add("dataItem"); Mockito.doReturn(data).when(dataService).getData()

В чём разница, почему связывание через Mockito.when считается предпочтительным и когда всё-таки приходится использовать методы Mockito.do. ? Обратите внимание: в первой реализации при задании поведения метода (в данном случае getAllData() ) сначала выполняется вызов ещё не переопределённой его версии, и только потом, в недрах Mockito, происходит переопределение. Во второй же такого вызова не происходит — методу Stubber.when передаётся непосредственно mock, а уже у возвращённого этим методом объекта того же типа, но другой природы совершается вызов переопределяемого метода. Эта разница всё и определяет. Связывание через Mockito.do. никак не контролирует на стадии компиляции то, какой переопределяемый метод я вызову и совместим ли он по типу с заданным возвращаемым значением. Поэтому обычно Mockito.when предпочтительнее — там с этим ошибки быть не может. Зато возможны случаи, когда я хочу избежать вызова непереопределённого метода — для свежесозданного mock такой вызов вполне приемлем, но если я ранее уже переопределил этот метод или имею дело со spy, он может оказаться нежелательным, а при выбрасывании исключения и вовсе не позволит выполнить нужное переопределение. И вот тут на помощь приходит связывание через Mockito.do. .

Ещё одна ситуация, где не обойтись без методов Mockito.do. , — переопределение метода, возвращающего void : ожидающий параметра Mockito.when с таким методом работать не может. Mockito.doReturn тут, понятно, не у дел, зато есть Mockito.doThrow , Mockito.doAnswer и достаточно редко пригождающийся Mockito.doNothing .

Далее я рассмотрю чуть подробнее способы задания условий и результатов вызовов. Я буду рассматривать только связывание через Mockito.when — альтернативный способ практически полностью аналогичен в обращении.

Задание условий вызова

Пример выше касается метода без параметров, и связанное с ним условие вызова возможно одно — сам факт вызова. Как только появляются параметры, ситуация становится сложнее. Как минимум, для вызова метода, поведение которого я задаю, мне нужно что-то ему передать. Но важнее другое: может оказаться, что задаваемую реакцию я хочу получать не всегда, а только при вызове с параметрами, отвечающими определённым требованиям. DataService имеет вот такой метод:

String getDataItemById(String id) < // some code. >

Если мне нужно задать реакцию на любой вызов этого метода независимо от аргументов, я должен воспользоваться методом Mockito.any :

Mockito.when(dataService.getDataItemById(any())) .thenReturn("dataItem");

Если же мне требуется, чтобы mock реагировал только на определённое значение аргумента, можно использовать непосредственно это значение или методы Mockito.eq (когда речь об эквивалентности) либо Mockito.same (когда требуется сравнение ссылок):

Mockito.when(dataService.getDataItemById("idValue")) .thenReturn("dataItem"); // or Mockito.when(dataService.getDataItemById(Mockito.eq("idValue"))) .thenReturn("dataItem");

А если я хочу, чтобы аргумент отвечал каким-то требованиям, для этого есть ряд удобных специализированных статических методов того же класса Mockito (например, строки можно проверить на содержание в начале или в конце определённой последовательности символов, соответствие паттерну и др.). Также имеется общий метод Mockito.argThat (и его аналоги для примитивных типов), принимающий реализацию функционального интерфейса ArgumentMatcher:

Mockito.when(dataService.getDataById( Mockito.argThat(arg -> arg == null || arg.length() > 5))) .thenReturn("dataItem");

Классы ArgumentMatchers и AdditionalMatchers позволяют работать с некоторыми полезными готовыми реализациями этого интерфейса. Например, AdditionalMatchers.or и AdditionalMatchers.and позволяют комбинировать другие матчеры (обратите внимание: статические методы этих классов не возвращают экземпляры матчеров, а только обращаются к ним!)

Для одного и того же метода можно задать поведение несколько раз с разными требованиями к аргументам, и все определённые таким образом модели поведения будут действовать одновременно. Разумеется, в каких-то случаях они могут пересекаться — скажем, я потребую вернуть один результат при получении значения int параметра меньше 5 и другой — при получении чётного значения. В такой ситуации приоритет имеет то поведение, которое задано позже. Поэтому при задании сложных схем поведения следует начинать с самых слабых требований (в пределе — any() ) и уже затем переходить к более специфическим.

При работе с методами с более чем одним аргументом заданные требования комбинируются в соответствии с логическим И, то есть для получения заданного результата КАЖДЫЙ из аргументов должен отвечать поставленному требованию. Я не нашёл способа скомбинировать требования произвольным образом, хотя, возможно, он существует.

Кроме того, при задании поведения такого метода нельзя комбинировать использующие матчеры статические методы Mockito и прямую передачу значений. Используйте Mockito.eq или Mockito.same .

Задание результатов вызова

После того, как метод mock-объекта вызван, объект должен отреагировать на вызов. Основные возможные последствия — возвращение результата и выбрасывание исключения, и именно на эти варианты в первую очередь рассчитан инструментарий Mockito.

В простейшем случае, уже показанном выше, реакция на вызов — возвращение значения. Приведу его код ещё раз:

List data = new ArrayList<>(); data.add("dataItem"); Mockito.when(dataService.getAllData()).thenReturn(data);

Обратите внимание: вернуть можно только объект, отдельных методов для примитивов не предусмотрено. Поэтому, если метод возвращает примитивное значение, в такой ситуации будет происходит un/boxing. В большинстве случаев это никак не мешает, но если компилятор считает иначе, придётся как-то с ним договариваться… или смириться с его предупреждениями.

Бросить исключения ничуть не сложнее:

Mockito.when(dataService.getDataById("invalidId")) .thenThrow(new IllegalArgumentException());

Есть и другой способ: можно создать объект исключения и бросить непосредственно его, а можно предоставить Mockito только класс исключения, чтобы оно было создано автоматически:

Mockito.when(dataService.getDataById("invalidId")) .thenThrow(IllegalArgumentException.class);

В обоих случаях синтаксис позволяет использовать и checked исключения, однако Mockito не позволит запустить такой тест, если тип исключения не соответствует методу, который я хочу заставить бросить это исключение.

При использовании класса как параметра конструкторы (даже без параметров), а равно и прямая инициализация полей, игнорируются — объект создаётся в обход них (в конце концов, это же Mockito!), так что все поля брошенного исключения будут равны null . Поэтому, если для вас имеет значение содержимое исключения (допустим, какое-нибудь поле type , у которого есть значение по умолчанию), придётся отказаться от этого способа и создавать исключения вручную.

Эти варианты реакции подходят, если в ответ на вызов с заданными условиями нужно всегда возвращать определённое, всегда одно и то же значение результата или выбрасывать всегда одинаковое исключение, и в большинстве случаев этих возможностей вполне достаточно. Но как быть, если требуется бо́льшая гибкость? Предположим, мой метод принимает коллекцию значений, а возвращает другую коллекцию значений, связанных с первыми одно к одному (например, это получение коллекции объектов данных по набору их ID), и я хочу в рамках теста использовать этот mock-объект неоднократно с разными наборами входных данных, получая каждый раз соответствующий результат. Можно, конечно, описать по отдельности реакцию на каждый конкретный набор параметров, но есть более удобное решение — метод .thenAnswer , он же .then . Он принимает реализацию функционального интерфейса Answer , единственный метод которого получает объект класса InvocationOnMock . У последнего я могу запросить параметры вызова метода (один по номеру или все сразу в виде массива) и поступить с ними, как мне заблагорассудится. Например, можно получить для каждого из элементов моей коллекции соответствующее ему значение, сформировать из них новую коллекцию и вернуть её (обратите внимание: желаемый результат просто возвращается, а не записывается в какое-то поле объекта-параметра, как можно было бы ожидать):

Mockito.when(dataService.getDataByIds(Mockito.any())) .thenAnswer(invocation -> invocation .>getArgument(0).stream() .map(id -> < switch (id) < case "a": return "dataItemA"; case "b": return "dataItemB"; default: return null; >>) .collect(Collectors.toList()));

Идеологически это что-то вроде написания модели реального метода: получение параметров, обработка, возвращение результата. В принципе ничто не мешает мне реализовать здесь и какую-то другую логику, дополнительно влияющую на что-нибудь ещё, но если такая необходимость возникает регулярно, это может указывать на то, что я заставляю mock-объекты выполнять несвойственные им задачи.

Есть также готовые реализации Answer , предоставляющие наиболее типичные варианты функционала, — например, AnswersWithDelay , ReturnsElementsOf и т. д.

Обратите внимание: типобезопасности InvocationOnMock не обеспечивает — аргументы возвращаются либо в виде массива Object[] , либо generic-методом.

Отдельно стоит упомянуть ещё один вариант реакции — thenCallRealMethod . Предназначение понятно из названия. Он действует как для mock-, так и для spy-объектов. В случае mock все поля объекта, к которым может обратиться код метода, будут опять-таки иметь значение null . Для spy же использование thenCallRealMethod означает возвращение к поведению spy по умолчанию; это может быть пригодится, если я переопределил поведение какого-то метода и теперь хочу вернуть прежнее.

Такой вариант реакции также можно получить через thenAnswer : объект InvocationOnMock имеет метод callRealMethod() — это может пригодиться, если нужно «обернуть» вызов реального кода какой-то дополнительной логикой.

Все перечисленные методы OngoingStubbing возвращают также объект OngoingStubbing , у которого, в свою очередь, можно вызвать любой из них. Таким образом задаётся последовательность разных реакций на один и тот же вызов, совершённый несколько раз. Методы thenReturn и thenThrow имеют перегруженные версии, принимающие varargs. Они позволяют сделать то же самое компактнее.

Mockito.when(dataService.getDataById("a")) .thenReturn("valueA1", "valueA2") .thenThrow(IllegalArgumentException.class);

Здесь первый вызов метода с заданным параметром вернёт «valueA1 , второй — «valueA2 (не спрашивайте), а третий (как и все последующие) будет вызывать выбрасывание IllegalArgumentException .

Слежение за вызовами методов

Всё вышеописанное служит главным образом решению одной задачи: удовлетворить потребность тестируемого нами класса в объектах других классов (mock’и которых мы и создаём), обладающих нужным нам предсказуемым поведением. Может возникнуть другая, в некотором смысле обратная задача: убедиться в том, что тестируемый класс вызывает методы этих объектов нужное число раз, в нужном порядке и с нужными параметрами. Для этого предназначены методы семейства Mockito.verify. .

Простейший вариант, когда я проверяю факт однократного вызова метода на протяжении выполнения теста, выглядит так:

Mockito.verify(dataService).getDataById(Mockito.any());

Тест с такой конструкцией пройдёт успешно, если она находится после единственного за время выполнения теста вызова метода getDataById , и упадёт, если метод не был вызван или был вызван дважды и более. Заметьте, что на количество вызовов никак не влияет ни одно служебное выражение самого Mockito, включая задание поведения с помощью when , хотя в нём, казалось бы, и присутствует непосредственно вызов нужного метода у нужного mock-объекта. Это, однако, не значит, что с каждым новым вызовом when счётчик сбрасывается, — подсчёт идёт от самого создания mock’а, если только его не обнуляли специально (см. ниже).

То же самое можно выразить при помощи более гибкого перегруженного метода:

Mockito.verify(dataService, Mockito.times(1)) .getDataById(Mockito.any());

Здесь я могу указать ожидаемое количество вызовов при помощи метода Mockito.times ; для отсутствия вызовов существует также шоткат Mockito.never . Ещё здесь применимы Mockito.atLeast (с шоткатом Mockito.atLeastOnce для значения 1) и Mockito.atMost , устанавливающие соответственно минимальное и максимальное количество вызовов, и странный метод Mockito.only , проверяющий, что вызов метода был единственным обращением к данному mock-объекту вообще (т. е. другие методы вызваны не были).

Кроме того, одноимённые методы могут быть вызваны не непосредственно у Mockito , а у объектов классов VerificationAfterDelay и VerificationWithTimeout , возвращаемых соответственно методами Mockito.after и Mockito.timeout . Например:

Mockito.verify(dataService, Mockito.after(1000).times(1)) .getDataById(Mockito.any());

В этом случае проверка, не обнаружившая у mock нужного числа вызовов, не приводит сразу к падению теста, а сперва ждёт в течение заданного в миллисекундах периода времени в расчёте на то, что эти вызовы всё же будут совершены. Это возможность полезна при работе с многопоточным кодом. Между собой after и timeout различаются тем, что в первом случае проверка успешно проходит только после того, как заданный период завершится, а во втором — сразу после того, как требуемое условие окажется выполненным. Таким образом, при использовании timeout вызовов соответствующего метода может оказаться и больше требуемого — на успешность прохождения теста это не повлияет. Поэтому у VerificationWithTimeout нет методов never и atMost : с учётом принципа его работы в них нет смысла.

Как видите, в качестве параметра для вызываемого метода я использую здесь уже встречавшийся выше Mockito.any() . На его месте могут быть и другие обращения к матчерам, перечисленные там же, или непосредственно значения параметров — в этих случаях Mockito проверит количество вызовов заданного метода не вообще, а именно с параметрами, соответствующими заданным таким образом требованиям. Mock-объект сохраняет информацию об истории вызовов, и ничто не мешает подвергнуть её нескольким проверкам, если это нужно, например, так:

dataService.getDataById("a"); dataService.getDataById("b"); Mockito.verify(dataService, Mockito.times(2)).getDataById(Mockito.any()); Mockito.verify(dataService, Mockito.times(1)).getDataById("a"); Mockito.verify(dataService, Mockito.never()).getDataById("c"); dataService.getDataById("c"); Mockito.verify(dataService, Mockito.times(1)).getDataById("c"); Mockito.verifyNoMoreInteractions(dataService);

В конце я вызываю метод verifyNoMoreInteractions (он же verifyZeroInteractions ) — он проверяет отсутствие каких-либо неверифицированных (то есть не подпадающих ни под один из выполненных до этого вызовов verify ) обращений к моему mock-объекту — к любым его методам. Обратите внимание: метод принимает varargs, но это совсем не означает, как можно было бы подумать, что речь о проверке взаимодействия переданных ему объектов между собой!

Код выше проверяет факт определённого количества вызовов, но не то, в каком порядке они были совершены, а это тоже может понадобиться. Чтобы контролировать порядок, нужно получить объект InOrder :

InOrder inOrder = Mockito.inOrder(dataService);

Этот метод тоже принимает varargs; порядок добавления не важен — несколько переданных ему mock-объектов означают всего лишь, что полученный объект InOrder можно будет использовать для контроля за порядком вызова методов методов всех этих объектов относительно друг друга. Сам метод имеет методы verify с теми же сигнатурами, что и Mockito.verify :

inOrder.verify(dataService, times(2)).saveData(any()); inOrder.verify(dataService).getData();

Такой тест пройдёт только в том случае, если до приведённого фрагмента был дважды вызван метод saveData , а потом единожды — getData . Обратите внимание, что объект InOrder можно сгенерировать и до, и после подлежащих учёту вызовов — он в любом случае сработает.

Чтобы проконтролировать наличие вызова с определённым параметром, вполне достаточно матчеров, когда речь идёт о простых параметрах — строках, например. Если же речь об экземпляре какого-то более сложного класса со множеством полей, значения которых нужно проверить, может быть удобнее поступить иначе — перехватить параметр, с которым метод будет вызван, и проанализировать его отдельно. С этим поможет класс ArgumentCaptor и его метод capture() . Например:

DataSearchRequest request = new DataSearchRequest("idValue", new Date(System.currentTimeMillis()), 50); dataService.getDataByRequest(request); ArgumentCaptor requestCaptor = ArgumentCaptor.forClass(DataSearchRequest.class); Mockito.verify(dataService, times(1)).getDataByRequest(requestCaptor.capture()); assertThat(requestCaptor.getAllValues()).hasSize(1); DataSearchRequest capturedArgument = requestCaptor.getValue(); assertThat(capturedArgument.getId()).isNotNull(); assertThat(capturedArgument.getId()).isEqualTo("idValue"); assertThat(capturedArgument.getUpdatedBefore()).isAfterYear(1970); assertThat(capturedArgument.getLength()).isBetween(0, 100);

ArgumentCaptor хранит и предоставляет все значения соответствующего параметра, с которыми метод был вызван до того, как данный ArgumentCaptor был применён. getValue() возвращает последнее полученное значения, getAllValues() — все значения в порядке получения. Не очень удобно, что перехват параметра обязательно комбинируется с контролем количества вызовов, но это мелочь.

Mock-объекты как значения полей и аннотации Mockito

Если в классе теста есть поля, которым я хочу присвоить mock-объекты в качестве значений, это не обязательно делать вручную — достаточно снабдить его аннотацией @Mock и до каких-либо обращений к нему выполнить вот такой вызов:

MockitoAnnotations.initMocks(this);

(несмотря на название, этот метод предназначен не только для mock’ов, а задействует также и все нижеперечисленные аннотации)

Для spy предусмотрена аннотация @Spy — она в целом аналогична @Mock … но для spy может использоваться объект, на основе которого он будет создан, помните? Такой объект можно сразу указать в качестве значения аннотируемого поля, но можно и не указывать — тогда spy будет создан на основе класса.

Есть аннотация @Captor для создания экземпляров ArgumentCaptor — о ней отдельно, пожалуй, больше ничего не скажешь.

Ещё существует @InjectMocks . Помеченное таким образом поле инициализируется не каким-то исчадием Mockito, а самым что ни на есть настоящим объектом указанного класса. Его поля по возможности проинициализированы значениями mock-полей моего тестового класса, помеченных соответствующей аннотацией. Для этого используется конструктор с наибольшим числом параметров, сеттеры и так далее. Если какого-то объектного параметра конструктора не хватает, вместо него будет использован null , а вот параметр-примитив просто не позволит тесту сработать. В целом это похоже на маленькую и простую (и всё равно не такую уж примитивную) реализацию dependency injection.

Откат поведения к дефолтному и сессии Mockito

Если в моём тестовом классе всего один тестирующий метод, всё отлично: я создал mock (spy, argument captor. ), задал ему поведение, использовал его в тесте, всё. Но если их больше, а mock’и — это поля тестового класса, всё может оказаться сложнее. JUnit создаёт отдельный экземпляр тестового класса при вызове каждого из тестирующих методов, и у него здесь проблем нет, а вот у TestNG другой подход — экземпляр создаётся лишь один на все вызовы. Соответственно, поведение, определённое для mock’ов в одном из методов, будет воспроизводиться и в других, выполняемых после него, количество вызовов методов будет суммарным для них и т. д. А это, скорее всего, нежелательно — тем более, что порядок выполнения тестовых методов в общем случае не гарантирован.

Чтобы этого избежать, нужно до вызова каждого тестирующего метода тем или иным способом привести все mock-объекты в состояние по умолчанию. У TestNG есть для этого аннотации @BeforeMethod (и @AfterMethod для постобработки). После этого часто бывает удобно заново задать желаемое поведение для mock’ов в той степени, в которой оно общее для всех тестовых методов, чтобы на долю самих методов осталось лишь задание специфических деталей (это актуально и для JUnit — у него есть аналогичная аннотация @Before ).

Простой и очевидный, но не очень удобный способ, — использовать методы Mockito.reset и Mockito.clearInvocations . Оба принимают varargs, и передавать им нужно соответствующие mock’и. Первый возвращает к дефолтовому поведение методов, второй сбрасывает счётчики вызовов. Этот подход несколько гибче других: в некоторых редких случаях (например, когда тестирующие методы следуют друг за другом в заданном порядке и составляют единый сценарий) может оказаться, что поведение и/или счётчики части mock’ов я откатывать не хочу, — тогда достаточно не передавать их соответствующему методу. Также он порой может пригодиться, чтобы вернуть поведение mock’а по умолчанию в ходе работы тестирующего метода. Впрочем. авторы не рекомендуют пользоваться этими методами, утверждая, что необходимость в их вызове внутри тестирующего метода указывает на низкое качество тестов.

При использовании аннотаций доступен другой способ (пожалуй, самый популярный) — просто вызывать каждый раз MockitoAnnotations.initMocks(this); . Это позволит переинициализировать «начисто» все поля, помеченные аннотациями Mockito.

Ещё одно решение — использовать так называемые сессии Mockito. Именно его рекомендуют авторы. В начале сессии все mock-объекты инициализируются, а после работы обязательно должно быть выполнено её окончание (хотя mock’и продолжают оставаться функциональными и после него). Если я хочу создавать отдельную сессию для каждого тестового метода, то удобно создать поле типа MockitoSession , присвоить ему значение до вызова тестового метода и завершить сессию после. Вот пример для случая TestNG:

 @Mock DataService dataService; MockitoSession session; @BeforeMethod public void beforeMethod() < session = Mockito.mockitoSession() .initMocks(this) .startMocking(); >@Test public void testMethod() < // some code using the dataService field >@AfterMethod public void afterMethod()

Кроме того, сессии позволяют контролировать ещё некоторые аспекты работы — в частности, отслеживают «лишние» (заданные, но не задействованные в тесте) варианты поведения, если это нужно.

Что ещё?

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

  • настройка Mockito и отдельных mock-объектов при помощи MockSettings (а там есть достаточно любопытные вещи — например, можно заставить mock’и по умолчанию реализовать какие-то дополнительные интерфейсы);
  • работа с информацией о mock-объекте, хранящейся в MockingDetails ;
  • использование класса BDDMockito как альтернативы Mockito ;
  • интеграция с тестовыми фреймворками (классы для интеграции с JUnit есть непосредственно в составе основной библиотеки Mockito, есть и отдельные интеграционные библиотеки).

За освещением этих и других вопросов обращайтесь к официальной документации Mockito. Большая часть вышеизложенного более или менее полно описана непосредственно в javadoc’е класса Mockito .

Удивительная инъекция

Итак, я должен извиниться перед Джимом. Он написал рабочий тест на mockito и JUnit, и я сказал ему в обзоре, что не думаю, что он сделал то, что ожидал. Хотя я был неправ, этот сценарий выглядит для меня как ошибка . Назовите это желательными неожиданными побочными эффектами.

Представьте, что у вас есть следующие два класса:

public class Service < private String name; private Widget widget; public Service(String name, Widget widget) < this .name = name; this .widget = widget; public void execute() < widget.handle(name); public interface Widget < void handle(String thing);

Ничего захватывающего там нет …

Теперь давайте попробуем протестировать сервис с помощью теста Mockito (JUnit 5 здесь):

@ExtendWith (MockitoExtension. class )
class ServiceTest < private Widget widget; @InjectMocks private Service service = new Service( "Joe" , widget); service.execute(); verify(widget).handle( "Joe" );

Тест проходит. Но должно ли это?

Для чего InjectMocks ?

Для меня аннотация @InjectMocks предназначена для того, чтобы быть фабричным методом для создания чего-то, что зависит от фиктивных значений, выраженных с помощью @Mock в тесте. Вот как я обычно это использую, и я также ожидаю, что все объекты в моей экосистеме создаются с использованием инжектора конструктора.

Это хороший принцип проектирования, но это не определение того, что делает инструмент!

Что делает InjectMocks

Процесс применения этой аннотации просматривает поле, аннотированное @InjectMocks и @InjectMocks другой путь, если он null чем если он уже инициализирован. Будучи настолько чистым в том, что null путь является декларативным подходом к инжектированию конструктора, я совершенно не думал, что вставлять макеты может означать сделать это для существующего объекта. Документация также не совсем подтверждает это.

  • Если объекта нет, то @InjectMocks должен создать его
    • Он использует самый большой конструктор, который он может предоставить

    В довершение всего, @InjectMocks молча терпит неудачу, так что вы можете иметь неудачные тесты, не зная об этом.

    В довершение всего, некоторые люди, использующие MockitoAnnotations.initMocks() в своих тестах, помимо Mockito Runner, вызывают всевозможные странности . Серьезно, ребята, никогда не называйте это.

    Уроки выучены

    Э-э … прости Джим!

    Аннотация @InjectMocks пытается сделать все возможное, но чем сложнее сценарий, тем сложнее его предсказать.

    Использование двух сквозных техник для инициализации объекта кажется мне опасным и сложным для понимания подходом, но если он работает, то он может быть лучше альтернатив, если он задокументирован. Добавить комментарий!

    Возможно, должен существовать какой-то вид @InjectWithFactory котором вы можете объявить метод, который получает @Mock и который @Mock при создании с объектами @Mock , чтобы вы могли заполнить любые другие параметры из остального контекста теста.

    Или, может быть, мы просто привыкли к этой работе и забываем о том, легко ли это понять.

    Последняя мысль

    Я узнал, что Mockito делает в вышеописанном, создав тест и отладив библиотеку Mockito, чтобы выяснить, как она достигает результата. Я настоятельно рекомендую исследовать ваши наиболее часто используемые библиотеки таким образом. Вы узнаете что-то полезное!

    Смотрите оригинальную статью здесь: Удивительная инъекция

    Мнения, высказанные участниками Java Code Geeks, являются их собственными.

    Injecting mocks with Mockito does not work

    I’m using Mockito to test my Spring project, but the @InjectMocks seems not working in injecting a mocked service into another Spring service(bean). Here is my Spring service that I want to test:

    @Service public class CreateMailboxService < @Autowired UserInfoService mUserInfoService; // this should be mocked @Autowired LogicService mLogicService; // this should be autowired by Spring public void createMailbox() < // do mething System.out.println("test 2: " + mUserInfoService.getData()); >> 

    And below is the service that I want to mock:

    @Service public class UserInfoService < public String getData() < return "original text"; >> 

    My test code is here:

    @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = < "file:src/main/webapp/WEB-INF/spring/root-context.xml" >) public class CreateMailboxServiceMockTest < @Mock UserInfoService mUserInfoService; @InjectMocks @Autowired CreateMailboxService mCreateMailboxService; @Before public void setup() < MockitoAnnotations.initMocks(this); >@Test public void deleteWithPermission() < when(mUserInfoService.getData()).thenReturn("mocked text"); System.out.println("test 1: " + mUserInfoService.getData()); mCreateMailboxService.createMailbox(); >> 

    but the result would like

    test 1: mocked text test 2: original text // I want this be "mocked text", too 

    it seems that the CreateMailboxService didn’t get the mocked UserInfoService but using Spring’s autowired bean. Why is my @InjectMocks not working?

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *