Spy mockito java что это
Перейти к содержимому

Spy mockito java что это

  • автор:

Mockito — All about Spies

Himanshu Mittal

In this post, I will discuss the concept of Spy in context of Mockito, the difference between Mocks and Spies, and finally how to use Spy.

What is Spy?

The Spy is a wrapper created on an actual instance of the object that lets you call the real methods of that object unless the method is stubbed.
It also tracks all interactions with it.

To put it simply it lets you call the real implemented method with the freedom to stub methods that need not be tested.

It is based on the concept of partial mocking, where you want to test some real methods and mock only a few methods in a class.

This can be handy when:

  • When you don’t have the luxury to refactor the code.
  • When you want to only test few methods and mock others.
  • Example: In a class, you can stub the database interaction to return a specific object, so that database is not needed while running the test.

Spies should be used occasionally and carefully. Interesting article on usage of Spies.

Difference btw Mock & Spy (Mockito)
A mock is created from Class of a type and not from the actual instance. A mock does not call the real method, it is just proxy for actual implementations and used to track interactions with it.

A spy is a partial mock, that calls the real methods unless the method is explicitly stubbed.

Since Mockito does not mock final methods, so stubbing a final method for spying will not help.

Simple Spy Usage

There are 2 ways to initialize spy

  • Spy Annotation
    For this to work, you need to annotate the test class or use Rule Annotation. You can also enable them programmatically

Example:

  • Using Mockito.spy()

Important gotcha on spying real objects

When stubbing a method using spies , please use doReturn() family of methods.

when(Object) would result in calling the actual method that can throw exceptions.

Mockito – Using Spies

Navigating the complexities of Spring can be difficult, even for seasoned developers.

If you need direct, practical help and guidance with your own Spring work, Trifork’s CTO, Joris Kuipers, is running a closed-door call.

It’s free, but it’s limited to only 3 seats, so if you need it, I would join quickly and be sure to attend:

With more than 15 years of leading custom software development projects involving Spring, Joris has gained a lot of real-world experience, and this call is about sharing and helping the community.

Building or modernizing a Java enterprise web app has always been a long process, historically. Not even remotely quick.

That’s the main goal of Jmix is to make the process quick without losing flexibility — with the open-source RAD platform enabling fast development of business applications.

Critically, it has very minimal impact on your server’s performance, with most of the profiling work done separately — so it needs no server changes, agents or separate services.

Simply put, a single Java or Kotlin developer can now quickly implement an entire modular feature, from DB schema, data model, fine-grained access control, business logic, BPM, all the way to the UI.

Jmix supports both developer experiences – visual tools and coding, and a host of super useful plugins as well:

Slow MySQL query performance is all too common. Of course it is. A good way to go is, naturally, a dedicated profiler that actually understands the ins and outs of MySQL.

The Jet Profiler was built for MySQL only, so it can do things like real-time query performance, focus on most used tables or most frequent queries, quickly identify performance issues and basically help you optimize your queries.

Critically, it has very minimal impact on your server’s performance, with most of the profiling work done separately — so it needs no server changes, agents or separate services.

Basically, you install the desktop application, connect to your MySQL server, hit the record button, and you’ll have results within minutes:

The Kubernetes ecosystem is huge and quite complex, so it’s easy to forget about costs when trying out all of the exciting tools.

To avoid overspending on your Kubernetes cluster, definitely have a look at the free K8s cost monitoring tool from the automation platform CAST AI. You can view your costs in real time, allocate them, calculate burn rates for projects, spot anomalies or spikes, and get insightful reports you can share with your team.

Connect your cluster and start monitoring your K8s costs right away:

Get started with Spring 5 and Spring Boot 2, through the Learn Spring course:

1. Overview

In this tutorial, we’ll illustrate how to make the most out of spies in Mockito.

We’ll talk about the @Spy annotation and how to stub a spy. Finally, we’ll go into the difference between Mock and Spy.

Of course, for more Mockito goodness, have a look at the series here.

Further reading:

Mockito Verify Cookbook

Injecting Mockito Mocks into Spring Beans

Mockito's Mock Methods

2. Simple Spy Example

Let’s start with a simple example of how to use a spy.

Simply put, the API is Mockito.spy() to spy on a real object.

This will allow us to call all the normal methods of the object while still tracking every interaction, just as we would with a mock.

Now let’s do a quick example where we’ll spy on an existing ArrayList object:

Note how the real method add() is actually called and how the size of spyList becomes 2.

3. The @Spy Annotation

Next, let’s see how to use the @Spy annotation. We can use the @Spy annotation instead of spy():

To enable Mockito annotations (such as @Spy, @Mock, … ), we need to use @ExtendWith(MockitoExtension.class) that initializes mocks and handles strict stubbings.

4. Stubbing a Spy

Now let’s see how to stub a Spy. We can configure/override the behavior of a method using the same syntax we would use with a mock.

Here we’ll use doReturn() to override the size() method:

5. Mock vs Spy in Mockito

Let’s discuss the difference between Mock and Spy in Mockito. We won’t examine the theoretical differences between the two concepts, just how they differ within Mockito itself.

When Mockito creates a mock, it does so from the Class of a Type, not from an actual instance. The mock simply creates a bare-bones shell instance of the Class, entirely instrumented to track interactions with it.

On the other hand, the spy will wrap an existing instance. It will still behave in the same way as the normal instance; the only difference is that it will also be instrumented to track all the interactions with it.

Here we’ll create a mock of the ArrayList class:

As we can see, adding an element into the mocked list doesn’t actually add anything; it just calls the method with no other side effects.

A spy, on the other hand, will behave differently; it will actually call the real implementation of the add method and add the element to the underlying list:

6. Understanding the Mockito NotAMockException

In this final section, we’ll learn about the Mockito NotAMockException. This exception is one of the common exceptions we will likely encounter when misusing mocks or spies.

Let’s start by understanding the circumstances in which this exception can occur:

When we run this code snippet, we’ll get the following error:

Thankfully, it is quite clear from the Mockito error message what the problem is here. In our example, the list object is not a mock. The Mockito when() method expects a mock or spy object as the argument.

As we can also see, the Exception message even describes what a correct invocation should look like. Now that we have a better understanding of what the problem is, let’s fix it by following the recommendation:

Our example now behaves as expected, and we no longer see the Mockito NotAMockException.

7. Conclusion

In this brief article, we discussed the most useful examples of using Mockito spies.

We learned how to create a spy, use the @Spy annotation, stub a spy, and finally, the difference between Mock and Spy.

The implementation of all of these examples can be found on GitHub.

This is a Maven project, so it should be easy to import and run as it is.

Spy mockito java что это

A field annotated with @Spy can be initialized explicitly at declaration point. Alternatively, if you don’t provide the instance Mockito will try to find zero argument constructor (even private) and create an instance for you. But Mockito cannot instantiate inner classes, local classes, abstract classes and interfaces. For example this class can be instantiated by Mockito :

Important gotcha on spying real objects!
  1. Sometimes it’s impossible or impractical to use Mockito.when(Object) for stubbing spies. Therefore for spies it is recommended to always use doReturn | Answer | Throw() | CallRealMethod family of methods for stubbing. Example:
  2. Mockito *does not* delegate calls to the passed real instance, instead it actually creates a copy of it. So if you keep the real instance and interact with it, don’t expect the spied to be aware of those interaction and their effect on real instance state. The corollary is that when an *unstubbed* method is called *on the spy* but *not on the real instance*, you won’t see any effects on the real instance.
  3. Watch out for final methods. Mockito doesn’t mock final methods so the bottom line is: when you spy on real objects + you try to stub a final method = trouble. Also you won’t be able to verify those method as well.

One last warning : if you call MockitoAnnotations.initMocks(this) in a super class constructor then this will not work. It is because fields in subclass are only instantiated after super class constructor has returned. It’s better to use @Before. Instead you can also put initMocks() in your JUnit runner (@RunWith) or use the built-in MockitoJUnitRunner .

Note that the spy won’t have any annotations of the spied type, because CGLIB won’t rewrite them. It may troublesome for code that rely on the spy to have these annotations.

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

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

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

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

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

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

mock и spy

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

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

Готово — я получил экземпляр нужного мне класса. Он будет принят любым методом или конструктором, которому требуется параметр такого типа (например, конструктором того класса, который я хочу протестировать). Даже если далее его ожидает проверка с пристрастием, он её пройдёт: не только 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 и вписать в него строчку:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

В чём разница, почему связывание через 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 имеет вот такой метод:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

(несмотря на название, этот метод предназначен не только для 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:

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

Что ещё?

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

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

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

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

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