Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Spy в тестировании (Mockito)
Spy — это специальный тип тестового двойника (test double) в фреймворке Mockito, который позволяет частично мокировать реальный объект. В отличие от Mock, который полностью заменяет объект, Spy вызывает реальные методы оригинального объекта, но при этом позволяет следить за его вызовами и переопределять поведение отдельных методов.
Основное отличие: Mock vs Spy
Mock — полностью заменяет объект:
public class UserService {
private UserRepository repository;
public UserService(UserRepository repository) {
this.repository = repository;
}
public User getUser(String id) {
return repository.findById(id);
}
}
@Test
public void testWithMock() {
// Mock — полностью заменяет repository
UserRepository mockRepository = mock(UserRepository.class);
when(mockRepository.findById("1")).thenReturn(new User("1", "John"));
UserService service = new UserService(mockRepository);
User user = service.getUser("1");
// Используется заданное поведение, реальный метод не вызывается
assertThat(user.getName()).isEqualTo("John");
verify(mockRepository).findById("1");
}
Spy — вызывает реальные методы, но позволяет их мониторить:
@Test
public void testWithSpy() {
UserRepository realRepository = new UserRepository();
// Spy оборачивает реальный объект
UserRepository spyRepository = spy(realRepository);
UserService service = new UserService(spyRepository);
User user = service.getUser("1");
// Используется реальный метод findById()
assertThat(user).isNotNull();
verify(spyRepository).findById("1"); // Проверяем что метод был вызван
}
Создание Spy
1. Spy существующего объекта
@Test
public void spyOnRealObject() {
List<String> realList = new ArrayList<>();
// Spy оборачивает реальный список
List<String> spyList = spy(realList);
// Используются реальные методы
spyList.add("hello");
spyList.add("world");
assertThat(spyList).hasSize(2);
// Можем проверить вызовы
verify(spyList).add("hello");
verify(spyList).add("world");
}
2. Spy класса с помощью конструктора
@Test
public void spyWithClassType() {
// Mockito создает реальный объект и оборачивает его
UserRepository spyRepository = spy(new UserRepository());
// Реальный метод вызывается
User user = spyRepository.findById("1");
assertThat(user).isNotNull();
}
3. Spy с аннотацией @Spy
public class UserServiceTest {
@Spy
private UserRepository userRepository = new UserRepository();
@InjectMocks
private UserService userService;
@Test
public void testWithSpyAnnotation() {
User user = userService.getUser("1");
assertThat(user).isNotNull();
// Проверяем что метод был вызван
verify(userRepository).findById("1");
}
}
Переопределение методов в Spy
Spy позволяет переопределить поведение отдельных методов, сохраняя реальное поведение остальных:
@Test
public void partialMockingWithSpy() {
UserRepository spyRepository = spy(new UserRepository());
// Переопределяем один метод
when(spyRepository.findById("999")).thenReturn(null);
UserService service = new UserService(spyRepository);
// Для ID 1 используется реальный метод
User user1 = service.getUser("1");
assertThat(user1).isNotNull();
// Для ID 999 используется заданное поведение
User user999 = service.getUser("999");
assertThat(user999).isNull();
}
Использование doReturn vs when
Для Spy лучше использовать doReturn() вместо when(), чтобы избежать вызова реального метода:
@Test
public void spyWithDoReturn() {
UserRepository spyRepository = spy(new UserRepository());
// НЕПРАВИЛЬНО (вызовет реальный findById)
// when(spyRepository.findById("2")).thenReturn(null);
// ПРАВИЛЬНО (не вызывает реальный метод)
doReturn(null).when(spyRepository).findById("2");
User user = spyRepository.findById("2");
assertThat(user).isNull();
}
Практический пример с Spy
public class Logger {
public void log(String message) {
System.out.println("[LOG] " + message);
}
public void error(String message, Exception e) {
System.out.println("[ERROR] " + message + ": " + e.getMessage());
}
}
public class PaymentService {
private Logger logger;
private PaymentGateway gateway;
public PaymentService(Logger logger, PaymentGateway gateway) {
this.logger = logger;
this.gateway = gateway;
}
public boolean processPayment(double amount) {
logger.log("Processing payment of " + amount);
try {
gateway.charge(amount);
logger.log("Payment successful");
return true;
} catch (Exception e) {
logger.error("Payment failed", e);
return false;
}
}
}
@Test
public void testPaymentServiceWithSpy() {
// Spy logger для мониторинга логирования
Logger spyLogger = spy(new Logger());
// Mock gateway
PaymentGateway mockGateway = mock(PaymentGateway.class);
when(mockGateway.charge(100)).thenReturn(true);
PaymentService service = new PaymentService(spyLogger, mockGateway);
boolean result = service.processPayment(100);
// Проверяем результат
assertThat(result).isTrue();
// Проверяем что logger был вызван нужное количество раз
verify(spyLogger, times(2)).log(anyString());
verify(spyLogger, never()).error(anyString(), any());
}
Проверка вызовов методов в Spy
@Test
public void verifySpyMethodCalls() {
List<String> spyList = spy(new ArrayList<>());
spyList.add("item1");
spyList.add("item2");
spyList.get(0);
// Проверка что методы были вызваны
verify(spyList, times(2)).add(anyString());
verify(spyList).get(0);
// Проверка что метод не был вызван
verify(spyList, never()).remove(anyObject());
// Проверка порядка вызовов
InOrder inOrder = inOrder(spyList);
inOrder.verify(spyList).add("item1");
inOrder.verify(spyList).add("item2");
inOrder.verify(spyList).get(0);
}
Когда использовать Spy vs Mock
Используй Mock когда:
- Нужно полностью контролировать поведение объекта
- Тестируешь взаимодействие (какие методы вызваны)
- Зависимость сложная или дорогостоящая
- Нет готовой реализации
// Mock используется для внешних сервисов
PaymentGateway mockGateway = mock(PaymentGateway.class);
when(mockGateway.charge(amount)).thenReturn(true);
Используй Spy когда:
- Нужно большинство реального поведения
- Нужно переопределить только отдельные методы
- Тестируешь часть реального объекта
- Нужно следить за вызовами реальных методов
// Spy используется для частичного мокирования
Logger spyLogger = spy(new Logger());
verify(spyLogger).log(anyString());
Частые ошибки со Spy
// ОШИБКА 1: Использование when вместо doReturn
List<String> spyList = spy(new ArrayList<>());
when(spyList.get(0)).thenReturn("item"); // Выбросит IndexOutOfBoundsException
// ПРАВИЛЬНО:
doReturn("item").when(spyList).get(0);
// ОШИБКА 2: Забывают что реальный метод вызывается
UserRepository spyRepo = spy(new UserRepository());
when(spyRepo.findById("999")).thenReturn(null);
// Реальный метод всё равно вызовется, потом переопределится
// ПРАВИЛЬНО:
UserRepository spyRepo = spy(new UserRepository());
doReturn(null).when(spyRepo).findById("999");
Вывод
Spy — это мощный инструмент в Mockito для:
- Частичного мокирования объектов
- Мониторинга вызовов реальных методов
- Тестирования сложных взаимодействий
- Смешивания реального и заданного поведения
Он позволяет писать более реалистичные тесты, которые используют реальное поведение где возможно, но при этом дают полный контроль когда это необходимо.