Привет, Хабр!  Помню, как в одном из моих первых проектов, мы столкнулись с проблемой тестирования кода, который зависел от внешних сервисов и баз данных. Это было не только трудоемко, но и занимало уйму времени. И сразу стало понятно, что без мокирования не обойтись. Мокирование создать поддельные версии сложных объектов или систем, чтобы мы могли тестировать каждую часть нашего кода изолированно. Это упрощает написание тестов и бустит их выполнение. Несколько строк кода, и вы уже создаете моки для ваших сервисов и DAO! Mockito предоставляет понятный API для мокирования, который делает тесты не только проще в написании, но и более читабельными. Первое, что нам нужно сделать, это интегрировать Mockito в наш проект. Если вы работаете с Maven, то в ваш pom.xml необходимо добавить следующую зависимость: Для Gradle это будет выглядеть так: 5.x.x - это место для актуальной версии Mockito. Рекомендую всегда проверять последнюю версию на официальном сайте Mockito. На момент статьи версия 5.8.0. После того, как зависимости добавлены, убедитесь, что ваша IDE корректно обновила Если вы новичок в тестировании, будет полезно знать, что Mockito идеально работает с JUnit. Обычно, нет необходимости в дополнительной настройке, но чекните, что JUnit также добавлен в ваш проект. На мой взгляд JUnit самый лучший фрейм для мокито. JUnit используется для структурирования тестов и выполнения утверждений (assertions), в то время как Mockito предоставляет механизмы для создания моков и шпионов, которые необходимы для изоляции тестируемого кода от его зависимостей. Mockito тесно интегрирован с JUnit. Например, с помощью аннотации @RunWith(MockitoJUnitRunner.class) в JUnit 4 или @ExtendWith(MockitoExtension.class) в JUnit 5 можно инициализировать моки, созданные с помощью аннотации @Mock, автоматически. Совместное использование JUnit и Mockito упрощает тестирование. Mockito позволяет создавать моки для служб, репозиториев и других компонентов, таким образом, что тесты могут сосредоточиться на логике, не заботясь о внешних зависимостях, таких как различные бдшки и тп. Для начала, создадим простой мок-объект. Представим, у нас есть интерфейс DataService, который мы хотим замокировать: Используя Mockito, мы можем создать мок этого интерфейса очень просто: mockDataService - это мок-объект, который можно использовать в тестах. Он будет иметь поведение по умолчанию для всех методов (например, возвращать 0 для методов, возвращающих double). Чтобы мок был полезен, нам нужно определить его поведение. Допустим, мы хотим, чтобы метод retrieveData() возвращал определенное значение. Сделать это можно следующим образом: Теперь при вызове retrieveData() на мок-объекте mockDataService, он вернет 15.0. Теперь, когда у нас есть мок и мы настроили его поведение, мы можем использовать его в нашем тесте. Допустим, у нас есть класс DataProcessor, который зависит от DataService. Мы можем использовать мок DataService для тестирования DataProcessor: В тесте: Проверяем, что метод processData() корректно удваивает значение, полученное от DataService. Метод when-thenReturn - это, на мой взгляд, самая часто используемая форма стаббинга в Mockito. Он позволяет определить возвращаемое значение для метода мок-объекта. Допустим, у нас есть интерфейс CalculatorService: Мы можем создать мок для этого интерфейса и определить поведение метода add таким образом: Здесь мы задаем, что при вызове add(10.0, 20.0) мок должен вернуть 30.0. В то время как when-thenReturn идеально подходит для большинства сценариев, иногда мы сталкиваемся с некоторыми ограничительными нюансами, особенно когда работаем с методами, которые возвращают void. В этих случаях полезен doReturn-when. Предположим, у нас есть метод void printSum(double input1, double input2), который не возвращает значение, но мы хотим удостовериться, что он был вызван с определенными параметрами. Мы можем использовать doReturn-when для этой цели: Используем doNothing(), чтобы указать, что при вызове printSum(10.0, 20.0) ничего не должно происходить, но мы можем проверить, что вызов действительно был сделан. Допустим, у нас есть интерфейс OrderService, который мы хотим протестировать: Когда мы пишем тест, нам может понадобиться убедиться, что метод placeOrder был действительно вызван. Вот как мы можем это сделать с помощью Mockito: Здесь мы не только создали мок OrderService, но и проверили, что метод placeOrder был вызван с определенным заказом. Mockito позволяет нам идти еще дальше: мы можем проверить количество вызовов метода и точные параметры, с которыми он был вызван. Это полезно, когда один и тот же метод вызывается несколько раз с разными параметрами. В этом примере мы проверяем, что placeOrder был вызван ровно два раза с любыми заказами и что getOrderCount не был вызван вообще. Мокирование исключений полезно, когда вы хотите тестировать поведение вашего кода в условиях возникновения исключения. Допустим, у нас есть интерфейс FileReader, который читает файл и может выбрасывать IOException: Теперь, представим, что мы хотим протестировать поведение нашего класса FileProcessor, который зависит от FileReader, в ситуации, когда IOException выбрасывается. Чтобы протестировать этот случай, мы можем использовать Mockito для мокирования IOException: В этом тесте мы гарантируем, что когда метод readFile вызывается, IOException выбрасывается, и FileProcessor корректно обрабатывает это исключение. Mockito также позволяет мокировать исключения для конкретных параметров вызываемого метода. Это полезно, когда поведение метода зависит от предоставленных аргументов. Здесь мы гарантируем, что IOException будет выброшен только тогда, когда readFile вызывается с "specific.txt" в качестве аргумента. ArgumentMatchers  - это специальные условия, которые мы можем применять к аргументам вызываемых методов. Они позволяют определить правила для любых аргументов или их определенных комбинаций. Интерфейс UserService, который мы хотим протестировать: Теперь, мы хотим замокировать этот сервис и определить поведение метода findUser. Допустим, нам не важно, с какими аргументами вызывается метод - мы хотим вернуть определенного пользователя для любых значений. Вне зависимости от передаваемых в findUser аргументов, всегда будет возвращаться expectedUser. Теперь, если нам нужно определить поведение метода для конкретных аргументов: Здесь findUser возвращает specificUser только если вызывается с аргументами "Bob" и 25. ArgumentMatchers также полезны при верификации вызовов методов: Спай-объекты - это специальные моки, которые в основном ведут себя как обычные объекты, но позволяют переопределять поведение отдельных методов. Они мегаполезны, когда вам нужно тестировать реальный объект, но при этом изменить поведение некоторых его частей. Допустим, у нас есть класс ListManager, который мы хотим протестировать: Мы можем создать спай для этого класса следующим образом: Мы создали спай ListManager, который использует реальный метод createList. Однако, мы переопределили поведение метода getListSize, чтобы он возвращал 100, независимо от реального размера списка. Также как и с моками, мы можем использовать верификацию с спай-объектами: Здесь мы проверяем, что метод getListSize был действительно вызван на спай-объекте. Аннотация @Mock используется для создания мок-объектов. Вместо явного вызова Mockito.mock(Class), вы можете аннотировать поля вашего тестового класса. Рассмотрим очередной пример с интерфейсом DataService: Мы можем создать мок DataService с использованием @Mock: @InjectMocks автоматически вставляет мок-объекты в тестируемый объект. Это удобно, когда у вас есть сложные зависимости. Предположим, у нас есть класс DataProcessor, который зависит от DataService: Мы можем использовать @InjectMocks для автоматического внедрения мока DataService в DataProcessor: @Spy позволяет создать спай-объекты, которые частично мокируют реальные объекты. Допустим, у нас есть класс ListManager: Мы можем использовать @Spy для тестирования этого класса: @RunWith(MockitoJUnitRunner.class) - это аннотация JUnit, которая указывает, что тесты в классе должны запускаться с помощью специального "раннера" - MockitoJUnitRunner. Этот раннер инициализирует поля, аннотированные как @Mock, @Spy, @InjectMocks, автоматически, без необходимости вызывать MockitoAnnotations.initMocks(this) в каждом тестовом классе. Допустим, у нас есть следующие классы: Наша цель - протестировать DataProcessor, используя мок DataService. Для этого мы используем аннотацию @RunWith(MockitoJUnitRunner.class): Здесь MockitoJUnitRunner автоматически инициализирует mockDataService. Вам не нужно вызывать MockitoAnnotations.initMocks(this) в методе @Before. В Spring Boot, Mockito используется для создания моков или спай-объектов для Spring Beans. Это позволяет имитировать поведение этих бинов, таким образом, чтобы тестировать взаимодействие различных слоев приложения (например, контроллеров, сервисов и репозиториев) без необходимости взаимодействовать с реальными внешними системами, такими как базы данных. Допустим, у нас есть следующий сервис в приложении Spring Boot: И соответствующий репозиторий: Нам нужно протестировать метод getUserById в UserService, не обращаясь к реальной бд. Используем Spring Boot Test и Mockito для создания моков: В этом тесте: @ExtendWith(SpringExtension.class): Интегрирует тест с Spring TestContext Framework. @SpringBootTest: Говорит Spring Boot загрузить контекст приложения, чтобы тесты могли быть интеграционными. @Mock: Создает мок-объект для UserRepository. @InjectMocks: Автоматически вставляет мок userRepository в userService. Mockito - это не просто инструмент для мокирования, он повышает качества кода и упрощает тестирование. Не за горой новогодние праздники, в связи с этим хочу порекомендовать вам бесплатный урок, где вы создадите интерактивную Новогоднюю открытку - графическое приложение с использованием Java и LibGDX, с нуля. В ходе вебинара вы сможете предлагать идеи, которые хотелось бы реализовать в проекте. Зарегистрироваться на бесплатный урок.