← Назад к вопросам

Какие объекты заменял бы Mock в JUnit

2.0 Middle🔥 241 комментариев
#SOLID и паттерны проектирования#Тестирование

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Mock объекты в JUnit и Mockito

Mock объекты — это поддельные объекты, которые имитируют поведение реальных объектов. Они критичны для unit-тестирования, позволяя нам тестировать код в полной изоляции.

Что такое Mock?

Mock — это объект, который:

  • Имитирует поведение реального объекта
  • Отслеживает вызовы методов
  • Позволяет проверить, как он был использован
  • Позволяет задать ожидаемое поведение

Типы тестовых двойников

1. Dummy (Заглушка)

Dummy объект создаётся, но никогда не используется.

public class EmailServiceDummy implements EmailService {
    @Override
    public void sendEmail(String to, String message) {
        // Пусто — это заглушка
    }
}

public class NotificationServiceTest {
    @Test
    void testNotificationCreation() {
        EmailService dummy = new EmailServiceDummy();
        
        NotificationService service = new NotificationService(dummy);
        Notification notification = service.createNotification("Hello");
        
        assertThat(notification.getMessage()).isEqualTo("Hello");
        // dummy не используется в тесте
    }
}

2. Stub (Подвал)

Stub — это объект, который возвращает заранее определённые значения.

public class UserRepositoryStub implements UserRepository {
    @Override
    public User findById(Long id) {
        // Всегда возвращает одного и того же пользователя
        return new User(1L, "John Doe", "john@example.com");
    }
}

public class UserServiceTest {
    @Test
    void testGetUserById() {
        UserRepository stub = new UserRepositoryStub();
        UserService service = new UserService(stub);
        
        User user = service.getUserInfo(1L);
        
        assertThat(user.getName()).isEqualTo("John Doe");
    }
}

3. Mock (Мок)

Mock — это объект, который:

  • Проверяет, как он был вызван
  • Может проверить параметры вызовов
  • Может проверить количество вызовов
public class OrderServiceTest {
    @Test
    void testOrderCreationCallsPaymentService() {
        // Создаём mock
        PaymentService mockPayment = mock(PaymentService.class);
        OrderService service = new OrderService(mockPayment);
        
        // Задаём поведение
        when(mockPayment.charge(any(BigDecimal.class)))
            .thenReturn(true);
        
        // Вызываем сервис
        Order order = service.createOrder(new Order(BigDecimal.valueOf(100)));
        
        // Проверяем, как использовался mock
        verify(mockPayment, times(1)).charge(BigDecimal.valueOf(100));
    }
}

4. Spy (Шпион)

Spy — это частичный mock реального объекта. Он отслеживает вызовы, но вызывает реальные методы.

public class CalculatorSpyTest {
    @Test
    void testCalculatorWithSpy() {
        // Создаём spy на реальный объект
        Calculator calculator = spy(new Calculator());
        
        // Задаём поведение для одного метода
        when(calculator.add(2, 2)).thenReturn(5); // Переопределяем
        
        // Остальные методы работают реально
        assertThat(calculator.add(2, 2)).isEqualTo(5);     // mock
        assertThat(calculator.subtract(5, 3)).isEqualTo(2); // реально
        
        // Проверяем вызовы
        verify(calculator).add(2, 2);
        verify(calculator).subtract(5, 3);
    }
}

5. Fake (Подделка)

Fake — это рабочая реализация, но упрощённая.

public class InMemoryUserRepository implements UserRepository {
    private Map<Long, User> users = new HashMap<>();
    
    @Override
    public User findById(Long id) {
        return users.get(id);
    }
    
    @Override
    public void save(User user) {
        users.put(user.getId(), user);
    }
}

public class UserServiceTest {
    @Test
    void testGetUserById() {
        UserRepository fakeRepo = new InMemoryUserRepository();
        fakeRepo.save(new User(1L, "John"));
        
        UserService service = new UserService(fakeRepo);
        User user = service.getUserInfo(1L);
        
        assertThat(user.getName()).isEqualTo("John");
    }
}

Практические сценарии использования Mock

Сценарий 1: Mock External API

public class PaymentGatewayMockTest {
    @Test
    void testPaymentProcessing() {
        // Мокируем внешний сервис платежей
        PaymentGateway mockGateway = mock(PaymentGateway.class);
        
        when(mockGateway.processPayment(any(PaymentRequest.class)))
            .thenReturn(new PaymentResponse("SUCCESS", "TXN123"));
        
        PaymentProcessor processor = new PaymentProcessor(mockGateway);
        PaymentResponse response = processor.process(new PaymentRequest(100));
        
        assertThat(response.getStatus()).isEqualTo("SUCCESS");
        verify(mockGateway, times(1)).processPayment(any());
    }
}

Сценарий 2: Mock Database

public class UserServiceDatabaseMockTest {
    @Test
    void testCreateUserSavesToDatabase() {
        // Мокируем БД
        UserRepository mockDB = mock(UserRepository.class);
        
        when(mockDB.save(any(User.class)))
            .thenAnswer(invocation -> {
                User user = invocation.getArgument(0);
                user.setId(1L);  // Имитируем присвоение ID
                return user;
            });
        
        UserService service = new UserService(mockDB);
        User savedUser = service.createUser("John Doe", "john@example.com");
        
        assertThat(savedUser.getId()).isNotNull();
        verify(mockDB).save(any(User.class));
    }
}

Сценарий 3: Mock Logger

public class OrderServiceLoggingTest {
    @Test
    void testOrderServiceLogsEvents() {
        // Мокируем logger
        Logger mockLogger = mock(Logger.class);
        OrderService service = new OrderService(mockLogger);
        
        Order order = new Order(100);
        service.processOrder(order);
        
        // Проверяем, что логирование произошло
        verify(mockLogger).info("Order created: " + order.getId());
        verify(mockLogger).info("Order processed");
    }
}

Сценарий 4: Mock Email Service

public class NotificationServiceEmailMockTest {
    @Test
    void testNotificationSendsEmail() {
        EmailService mockEmail = mock(EmailService.class);
        NotificationService service = new NotificationService(mockEmail);
        
        service.notifyUser("user@example.com", "Welcome!");
        
        // Проверяем параметры вызова
        verify(mockEmail).sendEmail(
            eq("user@example.com"),
            contains("Welcome")
        );
    }
}

Сценарий 5: Mock с исключениями

public class ErrorHandlingTest {
    @Test
    void testHandlesPaymentException() {
        PaymentService mockPayment = mock(PaymentService.class);
        
        // Мокируем выброс исключения
        when(mockPayment.charge(any()))
            .thenThrow(new PaymentException("Network error"));
        
        OrderService service = new OrderService(mockPayment);
        
        assertThatThrownBy(() -> service.processOrder(new Order(100)))
            .isInstanceOf(OrderProcessingException.class)
            .hasMessageContaining("Payment failed");
    }
}

Важные конфигурации Mock с Mockito

public class AdvancedMockitoTest {
    @Test
    void demonstrateAdvancedMocking() {
        // 1. Mock с strict stubs (отслеживает неиспользуемые stubы)
        UserRepository mockRepo = mock(UserRepository.class, STRICT_STUBS);
        
        when(mockRepo.findById(1L))
            .thenReturn(new User(1L, "John"));
        
        // 2. InOrder verification (проверка порядка вызовов)
        UserRepository repo = mock(UserRepository.class);
        UserService service = new UserService(repo);
        
        service.updateUser(new User(1L, "Updated"));
        service.notifyUser(1L);
        
        InOrder inOrder = inOrder(repo);
        inOrder.verify(repo).save(any());
        inOrder.verify(repo).findById(1L);  // Проверяет, что save был ДО findById
        
        // 3. Verify no more interactions
        UserRepository anotherMock = mock(UserRepository.class);
        // Что-то делаем
        verify(anotherMock).findById(1L);
        verifyNoMoreInteractions(anotherMock);  // Проверяет нет других вызовов
        
        // 4. ArgumentCaptor (захват аргументов для проверки)
        UserRepository captorMock = mock(UserRepository.class);
        UserService captorService = new UserService(captorMock);
        
        User newUser = new User(null, "Jane");
        captorService.createUser(newUser);
        
        ArgumentCaptor<User> captor = ArgumentCaptor.forClass(User.class);
        verify(captorMock).save(captor.capture());
        
        User capturedUser = captor.getValue();
        assertThat(capturedUser.getName()).isEqualTo("Jane");
    }
}

Когда использовать Mock vs. реальные объекты

Используй MOCK если:
✓ Объект вызывает внешний API (HTTP, БД, очередь)
✓ Объект медленный (БД, файловая система)
✓ Объект имеет side effects (отправка email, логирование)
✓ Объект недетерминирован (случайные числа, время)
✓ Объект сложный и трудно инициализировать

Используй РЕАЛЬНЫЙ объект если:
✓ Объект простой и быстрый
✓ Это core бизнес-логика (хочешь тестировать реально)
✓ Нет внешних зависимостей
✓ Инициализация простая

Best Practices

@SpringBootTest
public class BestPracticesTest {
    @MockBean  // Для Spring контекста
    private PaymentService paymentService;
    
    @InjectMocks  // Автоматическое внедрение мокированных зависимостей
    private OrderService orderService;
    
    @Test
    void testWithBestPractices() {
        // 1. Конкретная подготовка (Arrange)
        when(paymentService.charge(BigDecimal.valueOf(100)))
            .thenReturn(true);
        
        // 2. Одно действие (Act)
        Order order = orderService.createOrder(new Order(BigDecimal.valueOf(100)));
        
        // 3. Одна проверка (Assert)
        assertThat(order.getStatus()).isEqualTo(OrderStatus.CONFIRMED);
        
        // 4. Проверка взаимодействия
        verify(paymentService, atLeastOnce()).charge(any());
    }
}

Выводы

Mock объекты замещают:

  1. External API (HTTP сервисы, платежные системы)
  2. Databases (Repository, DAO)
  3. Message Brokers (Kafka, RabbitMQ)
  4. File Systems (File I/O операции)
  5. Email Services (отправка письма)
  6. Logger (для проверки логирования)
  7. Expensive Operations (сложные вычисления)

Правило: мокируй внешние зависимости, тестируй реально core логику!