Mockito – testowanie z dependency mocking

TL;DR: Mockito to framework do tworzenia „fake” obiektów (mocków) w testach Java. Pozwala testować klasy w izolacji bez uruchamiania prawdziwych baz danych czy serwisów zewnętrznych. Używasz when().thenReturn() do definiowania zachowania mocków i verify() do sprawdzania interakcji.

Pisanie dobrych unit testów w Javie często napotyka na problem zależności. Jak przetestować serwis który łączy się z bazą danych, wysyła emaile i komunikuje się z API zewnętrznym? Mockito rozwiązuje ten problem elegancko – pozwala zastąpić prawdziwe zależności kontrolowanymi „imitacjami”.

Dlaczego Mockito jest ważne

W 2016 roku Mockito to de facto standard mockowania w Javie. Wspierany przez Google, używany w Spring Framework, preferowany przez zespoły enterprise. Mockito 2.0 wprowadza nowe możliwości ale zachowuje kompatybilność wsteczną.

Szybkie i niezawodne testy to kluczowy element CI/CD. Mockito pozwala pisać testy które działają w milisekundach zamiast sekund, nie wymagają infrastruktury zewnętrznej i zawsze dają deterministyczne wyniki.

Co się nauczysz:

  • Czym są mocki i kiedy ich używać
  • Jak konfigurować Mockito w projekcie
  • Podstawowe operacje: tworzenie mocków, stubbing, weryfikacja
  • Mockowanie różnych typów zależności
  • Best practices i częste pułapki w testach z Mockito

Wymagania wstępne:

  • Dobra znajomość Java i programowania obiektowego
  • Doświadczenie z JUnit 4
  • Rozumienie dependency injection
  • Znajomość wzorców projektowych (szczególnie Strategy, Observer)

Problem testowania z zależnościami

Analogia: Testujesz samochód ale nie chcesz palić prawdziwej benzyny, włączać silnika i jeździć po mieście. Mockito to „symulator jazdy” – testujesz sterowanie nie ruszając się z miejsca.

Zobaczmy typowy problem. Mamy serwis który przetwarza zamówienia:

public class OrderService {
    private PaymentGateway paymentGateway;
    private EmailService emailService;
    private OrderRepository orderRepository;
    
    public OrderService(PaymentGateway paymentGateway, 
                       EmailService emailService,
                       OrderRepository orderRepository) {
        this.paymentGateway = paymentGateway;
        this.emailService = emailService;
        this.orderRepository = orderRepository;
    }
    
    public boolean processOrder(Order order) {
        PaymentResult result = paymentGateway.charge(order.getAmount());
        if (!result.isSuccessful()) {
            return false;
        }
        
        order.setStatus(OrderStatus.PAID);
        orderRepository.save(order);
        
        emailService.sendConfirmation(order.getCustomerEmail(), order);
        
        return true;
    }
}
Problem bez mocków: Żeby przetestować tę metodę musielibyśmy mieć działającą bazę danych, bramkę płatności i serwer email. Testy byłyby wolne, niestabilne i kosztowne.

Konfiguracja Mockito

Dodaj Mockito do swojego projektu Maven:

<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-core</artifactId>
    <version>2.0.54-beta</version>
    <scope>test</scope>
</dependency>

Podstawowa klasa testowa z Mockito:

import static org.mockito.Mockito.*;
import static org.junit.Assert.*;

public class OrderServiceTest {
    
    @Mock
    private PaymentGateway paymentGateway;
    
    @Mock
    private EmailService emailService;
    
    @Mock
    private OrderRepository orderRepository;
    
    @InjectMocks
    private OrderService orderService;
    
    @Before
    public void setUp() {
        MockitoAnnotations.initMocks(this);
    }
}
Pro tip: @Mock tworzy mock, @InjectMocks automatycznie wstrzykuje mocki do testowanej klasy. MockitoAnnotations.initMocks() inicjalizuje wszystkie adnotacje.

Podstawowe operacje Mockito

Tworzenie mocków

PaymentGateway mock1 = mock(PaymentGateway.class);
PaymentGateway mock2 = Mockito.mock(PaymentGateway.class);

@Mock
private PaymentGateway paymentGateway;

PaymentGateway mock3 = mock(PaymentGateway.class, "PaymentGateway");

Stubbing – definiowanie zachowania

@Test
public void shouldProcessOrderSuccessfully() {
    Order order = new Order("customer@example.com", new BigDecimal("100.00"));
    PaymentResult successfulPayment = new PaymentResult(true, "TX123");
    
    when(paymentGateway.charge(any(BigDecimal.class)))
        .thenReturn(successfulPayment);
    
    boolean result = orderService.processOrder(order);
    
    assertTrue(result);
    assertEquals(OrderStatus.PAID, order.getStatus());
}

Weryfikacja interakcji

@Test
public void shouldSaveOrderAndSendEmail() {
    Order order = new Order("customer@example.com", new BigDecimal("100.00"));
    when(paymentGateway.charge(any(BigDecimal.class)))
        .thenReturn(new PaymentResult(true, "TX123"));
    
    orderService.processOrder(order);
    
    verify(orderRepository).save(order);
    verify(emailService).sendConfirmation("customer@example.com", order);
    verify(paymentGateway, times(1)).charge(any(BigDecimal.class));
}

Zaawansowane stubbing

when(userService.findById(1L)).thenReturn(user);
when(userService.findById(-1L)).thenThrow(new IllegalArgumentException());

when(paymentGateway.charge(any()))
    .thenReturn(successResult)
    .thenThrow(new PaymentException())
    .thenReturn(successResult);

when(userService.findById(anyLong())).thenCallRealMethod();

when(calculator.add(anyInt(), anyInt())).thenAnswer(invocation -> {
    int a = invocation.getArgumentAt(0, Integer.class);
    int b = invocation.getArgumentAt(1, Integer.class);
    return a + b;
});

Argument Matchers

when(userService.findById(anyLong())).thenReturn(user);
when(emailService.send(anyString(), any(Email.class))).thenReturn(true);
when(userService.findById(123L)).thenReturn(user);

when(orderService.findByStatusAndDate(eq(OrderStatus.PENDING), any(LocalDate.class)))
    .thenReturn(orders);

when(validator.isValid(argThat(email -> email.contains("@")))).thenReturn(true);

ArgumentCaptor<Order> orderCaptor = ArgumentCaptor.forClass(Order.class);
verify(orderRepository).save(orderCaptor.capture());
Order savedOrder = orderCaptor.getValue();
assertEquals(OrderStatus.PAID, savedOrder.getStatus());
Uwaga: Nie mieszaj argument matcherów z konkretnymi wartościami w jednym wywołaniu. Użyj albo matcherów albo konkretnych wartości.

Spying – częściowe mockowanie

@Spy
private List<String> spyList = new ArrayList<>();

@Test
public void testSpy() {
    spyList.add("element");
    assertEquals(1, spyList.size());
    
    doReturn(100).when(spyList).size();
    assertEquals(100, spyList.size());
    
    verify(spyList).add("element");
}

Weryfikacja interakcji

verify(userService).save(user);
verify(emailService, times(3)).send(anyString());
verify(paymentGateway, never()).refund(any());
verify(auditService, atLeastOnce()).log(anyString());

InOrder inOrder = inOrder(paymentGateway, orderRepository, emailService);
inOrder.verify(paymentGateway).charge(any());
inOrder.verify(orderRepository).save(any());
inOrder.verify(emailService).send(anyString());

verify(asyncService, timeout(1000)).processAsync(any());
verifyNoMoreInteractions(userService);

Best practices

✅ Dobre praktyki:

  • Mockuj tylko bezpośrednie zależności – nie mockuj obiektów które tworzysz w testowanej metodzie
  • Używaj @InjectMocks – automatyczne wstrzykiwanie jest wygodniejsze
  • Preferuj when().thenReturn() – bardziej czytelne niż deprecated syntax
  • Weryfikuj zachowania, nie implementację – testuj efekty, nie konkretne wywołania
Błąd #1: Mockowanie value objects (String, Integer) – te obiekty powinny być prawdziwe, nie mockowane.
Błąd #2: Zbyt dużo mocków w jednym teście – może oznaczać że klasa ma za dużo odpowiedzialności.
Błąd #3: Testowanie implementacji zamiast zachowania – verify() powinno sprawdzać efekty biznesowe.
Kiedy używać mocków a kiedy prawdziwych obiektów?

Mockuj zewnętrzne zależności (baza danych, API), zachowaj prawdziwe value objects i obiekty bez side effects. Zasada: mockuj na granicy systemu.

Czy mogę mockować static metody?

Mockito 2.0 nie obsługuje static methods. Użyj PowerMock lub refaktoruj kod żeby unikać static dependencies w testowanym kodzie.

Jaka jest różnica między Mock a Stub?

Stub to obiekt z pre-programowanymi odpowiedziami. Mock to stub + weryfikacja interakcji. Mockito łączy oba pojęcia w jednym obiekcie.

Czy powinienem mockować wszystkie zależności?

Nie zawsze. Mockuj zależności które mają side effects lub są wolne. Proste value objects i utility classes mogą być prawdziwe.

Jak debugować problemy z Mockito?

Użyj verbose logging: when(mock.method()).thenReturn(value).getMock() pokazuje szczegóły. Sprawdź argument matchery – częsta przyczyna problemów.

Przydatne zasoby:

🚀 Zadanie dla Ciebie

Stwórz testy dla klasy BookService która:

  • Wyszukuje książki przez BookRepository
  • Sprawdza dostępność przez InventoryService
  • Rezerwuje książkę przez ReservationService
  • Wysyła powiadomienie przez NotificationService

Napisz testy sprawdzające:

  • Udaną rezerwację dostępnej książki
  • Obsługę błędu gdy książka nie istnieje
  • Obsługę błędu gdy książka jest niedostępna
  • Czy powiadomienie jest wysyłane tylko przy udanej rezerwacji

Masz pytania o Mockito? Podziel się swoimi doświadczeniami w komentarzach – mockowanie na początku może wydawać się skomplikowane, ale szybko staje się naturalną częścią testów!

Zostaw komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *

Przewijanie do góry