Как следует обрабатывать зависимости класса при написании модульных (Unit) тестов?

«Как следует обрабатывать зависимости класса при написании модульных (Unit) тестов?» — вопрос из категории Тестирование, который задают на 10% собеседований Java Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Зависимости класса должны быть изолированы с помощью тестовых двойников (Test Doubles), таких как моки (mocks) или стабы (stubs). Это позволяет тестировать логику класса в полной изоляции, делая тесты быстрыми, стабильными и не зависящими от внешних систем.

Основные подходы:

  • Внедрение зависимостей (DI): Зависимости передаются через конструктор или сеттеры, что позволяет легко подменять их тестовыми двойниками.
  • Использование библиотек мокирования: Например, Mockito, EasyMock.

Пример с Mockito и JUnit 5:

class OrderServiceTest {
    @Mock
    private PaymentGateway paymentGatewayMock; // Зависимость-мок
    @Mock
    private InventoryService inventoryServiceMock;

    @InjectMocks
    private OrderService orderService; // Зависимости внедряются сюда

    @BeforeEach
    void setUp() {
        MockitoAnnotations.openMocks(this);
    }

    @Test
    void processOrder_ShouldChargePayment() {
        // 1. Arrange (Подготовка): Настраиваем поведение мока.
        when(inventoryServiceMock.isInStock("item123")).thenReturn(true);
        when(paymentGatewayMock.charge(anyDouble())).thenReturn(true);

        // 2. Act (Действие): Вызываем тестируемый метод.
        boolean result = orderService.processOrder("item123", 100.0);

        // 3. Assert (Проверка): Проверяем результат и взаимодействие.
        assertTrue(result);
        verify(paymentGatewayMock).charge(100.0); // Проверяем, что метод был вызван.
    }
}

Ключевые принципы:

  1. Изоляция: Тест падает только при изменении логики тестируемого класса, а не его зависимостей.
  2. Предсказуемость: Поведение моков контролируется в тесте.
  3. Скорость: Моки работают в памяти, без сетевых вызовов или доступа к БД.