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ą.
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
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; } }
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); } }
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());
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
Mockuj zewnętrzne zależności (baza danych, API), zachowaj prawdziwe value objects i obiekty bez side effects. Zasada: mockuj na granicy systemu.
Mockito 2.0 nie obsługuje static methods. Użyj PowerMock lub refaktoruj kod żeby unikać static dependencies w testowanym kodzie.
Stub to obiekt z pre-programowanymi odpowiedziami. Mock to stub + weryfikacja interakcji. Mockito łączy oba pojęcia w jednym obiekcie.
Nie zawsze. Mockuj zależności które mają side effects lub są wolne. Proste value objects i utility classes mogą być prawdziwe.
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!