Как работает Mockito и для чего его используют?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Mockito: Фреймворк для Моков в Тестировании
Mockito — это популярный фреймворк для создания mock-объектов (подделок) в unit-тестах на Java. Он позволяет изолировать тестируемый код от его зависимостей, обеспечивая контролируемую и предсказуемую среду тестирования.
Назначение Mockito
Основные задачи:
- Изоляция кода: тестировать логику в отрыве от внешних зависимостей
- Контроль поведения: определять, что должны возвращать зависимости
- Проверка взаимодействия: убедиться, что методы вызывались правильно
- Упрощение тестов: избежать сложной инициализации реальных объектов
- Скорость тестирования: мокированные объекты работают быстрее реальных
Основные Концепции
1. Mock объекты Подделка реального объекта, которая отслеживает вызовы методов и возвращает заранее определённые значения.
2. Stub (заглушка) Мок, который просто возвращает фиксированное значение.
3. Spy (шпион) Частичный мок реального объекта, который следит за вызовами но сохраняет оригинальное поведение.
Основное Использование
Создание простого мока:
import static org.mockito.Mockito.*;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
public class OrderServiceTest {
@Test
public void testOrder() {
// Создаём мок
PaymentService paymentService = mock(PaymentService.class);
// Настраиваем поведение
when(paymentService.charge(100)).thenReturn(true);
// Используем в коде
OrderService orderService = new OrderService(paymentService);
boolean result = orderService.placeOrder(100);
// Проверяем результат
assertTrue(result);
// Проверяем, что метод был вызван
verify(paymentService).charge(100);
}
}
Настройка Поведения Мока
1. Возвращаемые значения (Stubbing)
public class UserServiceTest {
@Test
public void testFindUser() {
UserRepository repo = mock(UserRepository.class);
User user = new User(1, "Alice");
// Простой stub
when(repo.findById(1)).thenReturn(user);
// Несколько вызовов
when(repo.findById(2))
.thenReturn(new User(2, "Bob"))
.thenReturn(new User(2, "Bob Updated"));
// Исключения
when(repo.findById(999)).thenThrow(new NotFoundException());
UserService service = new UserService(repo);
assertEquals("Alice", service.getUserName(1));
}
}
2. Аргументные Матчеры (Argument Matchers)
@Test
public void testWithMatchers() {
PaymentService payment = mock(PaymentService.class);
// Любое целое число
when(payment.charge(anyInt())).thenReturn(true);
// Любая строка
when(payment.refund(anyString())).thenReturn(true);
// Конкретное значение
when(payment.charge(eq(100))).thenReturn(true);
// Проверка диапазона
when(payment.charge(gt(50))).thenReturn(true);
}
Проверка Вызовов (Verify)
1. Базовая проверка
@Test
public void testVerifyCall() {
Logger logger = mock(Logger.class);
ErrorHandler handler = new ErrorHandler(logger);
handler.handleError("Critical error");
// Проверяем, что метод был вызван
verify(logger).error("Critical error");
}
2. Количество вызовов
@Test
public void testVerifyTimes() {
Database db = mock(Database.class);
Cache cache = new Cache(db);
cache.get("key1");
cache.get("key1"); // не должна обращаться к БД
verify(db, times(1)).query("key1"); // только один раз
verify(db, never()).query("key2");
verify(db, atLeast(1)).query("key1");
verify(db, atMost(1)).query("key1");
}
3. Порядок вызовов
@Test
public void testCallOrder() {
PaymentService payment = mock(PaymentService.class);
NotificationService notify = mock(NotificationService.class);
OrderService service = new OrderService(payment, notify);
service.processOrder(100);
// Проверяем порядок
InOrder inOrder = inOrder(payment, notify);
inOrder.verify(payment).charge(100);
inOrder.verify(notify).sendConfirmation();
}
Spy (Шпион на реальном объекте)
@Test
public void testWithSpy() {
// Реальный список
List<String> realList = new ArrayList<>();
List<String> spyList = spy(realList);
// Подменяем только определённый метод
when(spyList.size()).thenReturn(100);
// Остальное работает как обычно
spyList.add("item1");
assertEquals(1, realList.size());
assertEquals(100, spyList.size()); // мокированный метод
// Можно проверять реальные вызовы
verify(spyList).add("item1");
}
Аннотации (Annotation-Based)
import org.mockito.Mock;
import org.mockito.InjectMocks;
import org.mockito.junit.jupiter.MockitoExtension;
import org.junit.jupiter.api.extension.ExtendWith;
@ExtendWith(MockitoExtension.class)
public class ServiceTest {
@Mock
private UserRepository userRepo;
@Mock
private EmailService emailService;
@InjectMocks
private UserService userService; // автоматически инъектит моки
@Test
public void test() {
when(userRepo.findById(1)).thenReturn(new User(1, "John"));
// userService уже инициализирована с мокированным userRepo
User user = userService.getUser(1);
assertEquals("John", user.getName());
}
}
Реальный Пример: Тестирование Сервиса Заказов
@ExtendWith(MockitoExtension.class)
public class OrderServiceTest {
@Mock
private PaymentService paymentService;
@Mock
private InventoryService inventoryService;
@Mock
private NotificationService notificationService;
@InjectMocks
private OrderService orderService;
@Test
public void testSuccessfulOrder() {
// Подготовка
when(inventoryService.isAvailable("ITEM-123")).thenReturn(true);
when(paymentService.charge(150)).thenReturn(true);
// Выполнение
Order order = new Order("ITEM-123", 150);
boolean result = orderService.placeOrder(order);
// Проверка
assertTrue(result);
verify(paymentService).charge(150);
verify(inventoryService).reserve("ITEM-123");
verify(notificationService).sendConfirmation(any());
}
@Test
public void testOrderFailsWhenOutOfStock() {
when(inventoryService.isAvailable("ITEM-123")).thenReturn(false);
Order order = new Order("ITEM-123", 150);
boolean result = orderService.placeOrder(order);
assertFalse(result);
verify(paymentService, never()).charge(anyInt());
verify(notificationService).sendNotAvailable("ITEM-123");
}
}
Лучшие Практики
- Мокируй только зависимости, не тестируемый код
- Избегай излишних мокирований: только когда необходимо
- Используй Argument Matchers для гибкости
- Проверяй поведение, не реализацию: verify что вызовы произошли
- Держи тесты простыми: один сценарий на тест
- Используй @InjectMocks вместо ручной инъекции
Итоговое Резюме
Mockito — это мощный инструмент, который делает unit-тесты быстрыми, надёжными и независимыми от внешних сервисов. Он позволяет разработчикам сосредоточиться на тестировании конкретной логики, изолируя её от сложных зависимостей.