Pytania rekrutacyjne Mockito
55 pytań — 20 odpowiedzi dostępnych od razu
Podstawy Mockito
Czym jest Mockito i jakie problemy rozwiązuje w testowaniu kodu Java?
Odpowiedź w 30 sekund: Mockito to popularny framework do tworzenia obiektów typu mock w testach jednostkowych Javy. Pozwala odizolować testowaną klasę od jej zależności, dzięki czemu testy są szybkie, deterministyczne i skupione wyłącznie na logice jednej jednostki kodu.
Odpowiedź w 2 minuty: Mockito rozwiązuje problem testowania klas, które mają wiele zależności (bazy danych, usługi sieciowe, kolejki, inne serwisy). Zamiast uruchamiać prawdziwą bazę lub wołać prawdziwy serwis REST, podstawiamy mock — sztuczny obiekt, który zachowuje się tak, jak my mu każemy. To pozwala uniknąć efektów ubocznych i napisać test, który sprawdza wyłącznie zachowanie testowanej klasy.
Najczęstsze problemy, które Mockito rozwiązuje: izolacja testów (Single Responsibility w testach), kontrola nad zwracanymi wartościami zależności (when(...).thenReturn(...)), weryfikacja interakcji (verify(...)) — czyli sprawdzanie, czy testowany kod wywołał odpowiednią metodę z odpowiednimi argumentami. Dzięki temu możemy testować scenariusze trudne do odtworzenia w środowisku produkcyjnym — np. timeouty, błędy 500, puste odpowiedzi.
Mockito kładzie nacisk na czytelność API — używa stylu BDD (given/when/then), nie wymaga trybu record-replay (jak EasyMock) i działa z JUnit 4, JUnit 5 (@ExtendWith(MockitoExtension.class)) oraz TestNG.
Przykład kodu:
// Klasa testowana
class OrderService {
private final PaymentGateway gateway;
OrderService(PaymentGateway gateway) { this.gateway = gateway; }
boolean place(Order order) {
return gateway.charge(order.total()) == PaymentStatus.OK;
}
}
// Test z Mockito
@ExtendWith(MockitoExtension.class)
class OrderServiceTest {
@Mock PaymentGateway gateway; // tworzy mock automatycznie
@InjectMocks OrderService service; // wstrzykuje mocki przez konstruktor
@Test
void powinienZlozycZamowienieGdyPlatnoscOK() {
// given - definiujemy zachowanie zależności
when(gateway.charge(100)).thenReturn(PaymentStatus.OK);
// when - wywołujemy testowaną metodę
boolean result = service.place(new Order(100));
// then - sprawdzamy wynik i interakcję
assertTrue(result);
verify(gateway).charge(100);
}
}
Materiały
Czym różni się mock, stub, fake i spy w terminologii xUnit Patterns?
Odpowiedź w 30 sekund: W terminologii Gerarda Meszarosa (xUnit Test Patterns) wszystkie te obiekty to "test doubles" — zamienniki produkcyjnych zależności. Różnią się przeznaczeniem: stub dostarcza gotowych odpowiedzi, mock dodatkowo weryfikuje interakcje, fake to działająca lekka implementacja, a spy to częściowy mock owijający prawdziwy obiekt.
Odpowiedź w 2 minuty:
Stub zwraca z góry ustalone dane (state verification). Używamy go, gdy testowana metoda potrzebuje danych z zależności, ale nie obchodzi nas, czy i jak została wywołana. W Mockito stub to when(obj.method()).thenReturn(value).
Mock to stub plus weryfikacja interakcji (behavior verification). Sprawdzamy, czy testowany kod wywołał odpowiednią metodę zależności z odpowiednimi argumentami i odpowiednią liczbę razy. W Mockito użyjemy verify(mock).method(arg). W praktyce w Mockito każdy obiekt utworzony przez mock() może pełnić obie role.
Fake to lekka, ale działająca implementacja interfejsu — np. HashMap zamiast bazy danych, in-memory repository zamiast JpaRepository. Fake faktycznie wykonuje logikę, ale jest uproszczony — nadaje się do testów, ale nie do produkcji. Mockito sam fake'ów nie generuje — to ręcznie napisana klasa.
Spy to wrapper na prawdziwy obiekt. Domyślnie deleguje wywołania do oryginalnej implementacji, ale możemy nadpisać wybrane metody. Przydatny przy legacy code, gdzie nie da się łatwo wydzielić zależności. W Mockito: Mockito.spy(realObject). Uwaga — częste użycie spy często sygnalizuje, że kod wymaga refaktoryzacji.
Przykład kodu:
// Stub - tylko zwraca dane
List<String> stub = mock(List.class);
when(stub.get(0)).thenReturn("hello");
assertEquals("hello", stub.get(0));
// Mock - dodatkowo weryfikujemy wywołania
List<String> mock = mock(List.class);
mock.add("item");
verify(mock).add("item"); // czy metoda była wywołana?
verify(mock, times(1)).add(any());
// Fake - ręczna implementacja in-memory
class FakeUserRepo implements UserRepository {
private final Map<Long, User> store = new HashMap<>();
public User save(User u) { store.put(u.id(), u); return u; }
public User findById(Long id) { return store.get(id); }
}
// Spy - opakowuje prawdziwy obiekt
List<String> spy = spy(new ArrayList<>());
spy.add("real"); // dodaje naprawdę
when(spy.size()).thenReturn(99); // ale size() jest podrasowane
assertEquals(99, spy.size());
Tabela porównawcza:
flowchart TD
A["Test Doubles (xUnit Patterns)"] --> B[Stub]
A --> C[Mock]
A --> D[Fake]
A --> E[Spy]
B --> B1["Definicja: zwraca ustalone dane<br/>Zachowanie: brak weryfikacji<br/>Użycie: state verification"]
C --> C1["Definicja: stub + weryfikacja<br/>Zachowanie: sprawdza interakcje<br/>Użycie: behavior verification"]
D --> D1["Definicja: lekka implementacja<br/>Zachowanie: prawdziwa logika<br/>Użycie: złożone zależności in-memory"]
E --> E1["Definicja: wrapper na real object<br/>Zachowanie: częściowe mockowanie<br/>Użycie: legacy code"]
Materiały
Jaka jest różnica między Mockito a innymi frameworkami mockującymi (EasyMock, JMockit, PowerMock)?
Odpowiedź w 30 sekund: Mockito jest dziś dominującym frameworkiem dzięki czytelnemu API w stylu given-when-then. EasyMock używa modelu record-replay (bardziej toporny), JMockit oferuje potężne mockowanie statyki i finali, a PowerMock to rozszerzenie do Mockito/EasyMock pozwalające mockować static, private i konstruktory — coraz rzadziej potrzebne, bo nowoczesne Mockito (od 3.x) potrafi to natywnie.
Odpowiedź w 2 minuty:
Mockito to standardowy wybór w ekosystemie Java od ok. 2012 roku. Ma intuicyjne API (when().thenReturn(), verify()), dobre integracje z JUnit i Spring, i od wersji 3.4 potrafi mockować metody statyczne (mockStatic) oraz finalne klasy (przez mockito-inline).
EasyMock to starszy framework używający paradygmatu record-replay. Najpierw "nagrywamy" oczekiwane wywołania w trybie record, potem wywołujemy replay() i dopiero wtedy mock zachowuje się jak zaprogramowany. API jest mniej czytelne, dlatego EasyMock stracił popularność na rzecz Mockito.
JMockit to potężniejszy, ale bardziej skomplikowany framework używający agenta JVM (modyfikacja bytecode w runtime). Potrafi mockować praktycznie wszystko — static, final, private, konstruktory, a nawet java.lang.System. Cena to stroma krzywa uczenia i mniejsza społeczność.
PowerMock to nakładka na Mockito lub EasyMock dodająca możliwości mockowania static, private i konstruktorów (przez manipulację classloadera). Był popularny w czasach, gdy Mockito tego nie potrafiło. Dziś, gdy Mockito 3+ ma natywne mockStatic, PowerMock jest w zaniku — projekt nie jest już aktywnie rozwijany.
Przykład kodu:
// Mockito - given-when-then, intuicyjne
when(service.fetch()).thenReturn("data");
verify(service).fetch();
// EasyMock - record-replay (mniej czytelne)
expect(service.fetch()).andReturn("data");
replay(service); // przełączenie w tryb replay
service.fetch();
verify(service);
// JMockit - expectations block
new Expectations() {{
service.fetch(); result = "data";
}};
// PowerMock - mockowanie statyki (legacy podejście)
@RunWith(PowerMockRunner.class)
@PrepareForTest(Utils.class)
class Test {
@Test void test() {
mockStatic(Utils.class);
when(Utils.now()).thenReturn(1000L);
}
}
Porównanie frameworków:
flowchart LR
M[Mockito] --> M1["Styl: given-when-then<br/>Static: tak (mockStatic od 3.4)<br/>Final: tak (mockito-inline)<br/>Popularność: ★★★★★<br/>Status: aktywny"]
E[EasyMock] --> E1["Styl: record-replay<br/>Static: nie<br/>Final: nie natywnie<br/>Popularność: ★★<br/>Status: utrzymywany"]
J[JMockit] --> J1["Styl: expectations block<br/>Static: tak (JVM agent)<br/>Final: tak<br/>Popularność: ★★<br/>Status: utrzymywany"]
P[PowerMock] --> P1["Styl: rozszerza Mockito/EasyMock<br/>Static: tak (classloader hack)<br/>Final: tak<br/>Popularność: ★★ (zanik)<br/>Status: nieaktywny"]
Materiały
Jak dodać Mockito do projektu Maven i Gradle?
Odpowiedź w 30 sekund:
W Mavenie dodajemy mockito-core (i opcjonalnie mockito-junit-jupiter dla integracji z JUnit 5) jako zależność w zakresie test. W Gradle używamy konfiguracji testImplementation. Dla mockowania klas finalnych i metod statycznych potrzebujemy dodatkowo mockito-inline.
Odpowiedź w 2 minuty:
Mockito jest publikowane jako artefakty Maven Central pod groupId org.mockito. Najważniejsze artefakty to:
- mockito-core — rdzeń biblioteki, wystarczy do większości testów.
- mockito-junit-jupiter — integracja z JUnit 5 (
@ExtendWith(MockitoExtension.class)). - mockito-inline — alternatywny mock maker pozwalający mockować klasy finalne, metody finalne i statyczne. Od Mockito 5 ten mock maker jest domyślny — wówczas
mockito-corewystarcza. - mockito-bom — Bill of Materials, pozwala zarządzać wersjami wszystkich artefaktów spójnie.
W Spring Boot wystarczy zależność spring-boot-starter-test, która już zawiera Mockito w odpowiedniej wersji — wtedy nie deklarujemy jej osobno.
Pamiętaj o scope test w Mavenie lub testImplementation w Gradle — Mockito nie powinien trafiać do produkcyjnego classpath.
Przykład kodu:
<!-- Maven: pom.xml -->
<dependencies>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>5.11.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
<version>5.11.0</version>
<scope>test</scope>
</dependency>
<!-- opcjonalnie dla Mockito < 5 -->
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-inline</artifactId>
<version>5.2.0</version>
<scope>test</scope>
</dependency>
</dependencies>
// Gradle: build.gradle
dependencies {
testImplementation 'org.mockito:mockito-core:5.11.0'
testImplementation 'org.mockito:mockito-junit-jupiter:5.11.0'
// opcjonalnie dla mockowania final/static przy Mockito < 5
testImplementation 'org.mockito:mockito-inline:5.2.0'
}
// Gradle Kotlin DSL: build.gradle.kts
dependencies {
testImplementation("org.mockito:mockito-core:5.11.0")
testImplementation("org.mockito:mockito-junit-jupiter:5.11.0")
}
Materiały
Jakie są ograniczenia Mockito (final classes, static methods, private methods) i jak je obejść?
Odpowiedź w 30 sekund:
Klasyczne Mockito (do 2.x) nie potrafiło mockować klas finalnych, metod statycznych ani prywatnych. Od Mockito 3.4 mamy mockStatic dla statyki, a od Mockito 5 domyślny mock maker inline pozwala mockować klasy/metody finalne. Metod prywatnych nadal nie da się mockować — należy je refaktorować lub testować przez metody publiczne.
Odpowiedź w 2 minuty:
Klasy i metody finalne — historyczne ograniczenie wynikające z mechanizmu CGLib (Mockito tworzył podklasę mockowanej klasy, a finali nie można dziedziczyć). Rozwiązanie: artefakt mockito-inline (Mockito 2.7+) albo natywnie w Mockito 5 (gdzie inline jest domyślnym mock makerem). Inline używa Java Instrumentation API do modyfikacji bytecode.
Metody statyczne — od Mockito 3.4 dostępne jest Mockito.mockStatic(Class). Wymaga mockito-inline (lub Mockito 5). Należy używać try-with-resources, żeby mock działał tylko w scope testu i nie wpływał na inne testy.
Metody prywatne — Mockito nie wspiera ich i prawdopodobnie nie będzie. Filozofia jest taka, że metody prywatne to detal implementacji i powinny być testowane przez metody publiczne. Jeśli koniecznie musisz, opcje to: (1) refaktor — wyciągnięcie logiki do osobnej klasy/metody pakietowej, (2) refleksja (ReflectionTestUtils ze Spring Test), (3) PowerMock (ale to oznacza dług techniczny).
Konstruktory — od Mockito 3.5 jest mockConstruction(Class), działa podobnie do mockStatic.
Inne ograniczenia: Mockito nie mockuje equals(), hashCode(), toString() zachowuje domyślne. Nie mockuje też Optional ani enumów w sposób intuicyjny.
Przykład kodu:
// 1. Klasa finalna (Mockito 5 domyślnie, lub mockito-inline)
final class FinalService {
String name() { return "real"; }
}
@Test
void mockFinalClass() {
FinalService mock = mock(FinalService.class);
when(mock.name()).thenReturn("mocked");
assertEquals("mocked", mock.name());
}
// 2. Metoda statyczna - mockStatic
@Test
void mockStaticMethod() {
try (MockedStatic<Math> mocked = mockStatic(Math.class)) {
mocked.when(() -> Math.max(1, 2)).thenReturn(99);
assertEquals(99, Math.max(1, 2)); // mock działa
}
assertEquals(2, Math.max(1, 2)); // tu już prawdziwa
}
// 3. Konstruktor - mockConstruction
@Test
void mockConstructor() {
try (MockedConstruction<UserService> ignored =
mockConstruction(UserService.class,
(mock, ctx) -> when(mock.find(1L)).thenReturn(new User("Anna")))) {
UserService svc = new UserService(); // zwróci mock
assertEquals("Anna", svc.find(1L).name());
}
}
// 4. Metoda prywatna - przez refleksję (ostateczność)
class Holder { private String secret() { return "x"; } }
String result = (String) ReflectionTestUtils
.invokeMethod(new Holder(), "secret");
Tabela ograniczeń:
flowchart TD
O[Ograniczenia Mockito] --> F[Klasy finalne]
O --> S[Metody statyczne]
O --> P[Metody prywatne]
O --> C[Konstruktory]
F --> F1["Rozwiązanie: mockito-inline<br/>(Mockito 5 - domyślnie)<br/>Mechanizm: Java Instrumentation API"]
S --> S1["Rozwiązanie: Mockito.mockStatic()<br/>Wymaga: mockito-inline / Mockito 5<br/>Zasada: try-with-resources"]
P --> P1["Rozwiązanie: refleksja lub refactor<br/>Narzędzia: ReflectionTestUtils, PowerMock<br/>Zalecane: wyciągnij do klasy publicznej"]
C --> C1["Rozwiązanie: Mockito.mockConstruction()<br/>Wymaga: Mockito 3.5+<br/>Zasada: try-with-resources"]
Materiały
Tworzenie mocków
6. Jak tworzyć mocki za pomocą Mockito.mock() i adnotacji @Mock?
Mockito udostępnia dwa równoważne sposoby tworzenia mocków: programatyczne wywołanie Mockito.mock(Class) oraz deklaratywną adnotację @Mock. Wybór zależy od stylu testów oraz tego, czy chcemy korzystać z integracji z JUnit.
Sposób 1: Mockito.mock() (programatyczny)
import static org.mockito.Mockito.*;
class UserServiceTest {
@Test
void shouldReturnUserById() {
// Tworzenie mocka programatycznie
UserRepository repository = mock(UserRepository.class);
// Stubowanie zachowania
when(repository.findById(1L)).thenReturn(new User("Anna"));
UserService service = new UserService(repository);
User user = service.getUser(1L);
assertEquals("Anna", user.getName());
verify(repository).findById(1L);
}
}
Sposób 2: Adnotacja @Mock (deklaratywna)
@ExtendWith(MockitoExtension.class)
class UserServiceTest {
@Mock
private UserRepository repository;
@InjectMocks
private UserService service;
@Test
void shouldReturnUserById() {
when(repository.findById(1L)).thenReturn(new User("Anna"));
User user = service.getUser(1L);
assertEquals("Anna", user.getName());
}
}
Najważniejsze cechy:
Mockito.mock(Class)— natychmiastowe, działa wszędzie, nie wymaga frameworka testowego ani inicjalizacji.@Mock— czytelniejsze przy wielu mockach, automatycznie nadaje sensowną nazwę mocka (dla komunikatów weryfikacji), wymaga aktywacji przezMockitoExtension,MockitoAnnotations.openMocks(this)lubMockitoJUnitRunner.- Oba sposoby tworzą ten sam typ mocka — różnią się tylko sposobem inicjalizacji.
Źródło: site.mockito.org
7. Czym różni się Mockito.mock(Class) od @Mock z MockitoAnnotations.openMocks lub MockitoExtension?
Choć rezultat jest funkcjonalnie taki sam, sposób uzyskania mocka oraz koszt utrzymania kodu testowego znacząco się różnią.
flowchart LR
A["Mockito.mock(Class)<br/>(programatyczne)"] --> A1["Tworzy mock natychmiast<br/>w dowolnym miejscu"]
A --> A2["Brak wymaganej<br/>inicjalizacji"]
A --> A3["Nazwa mocka = nazwa klasy<br/>(mniej czytelne błędy)"]
B["@Mock<br/>(deklaratywne)"] --> B1["Wymaga otwarcia:<br/>MockitoExtension<br/>lub openMocks(this)"]
B --> B2["Nazwa mocka = nazwa pola<br/>(czytelne błędy)"]
B --> B3["Współpracuje z @InjectMocks,<br/>@Spy, @Captor"]
Aktywacja @Mock — dwa warianty:
// Wariant 1: MockitoExtension (zalecany dla JUnit 5)
@ExtendWith(MockitoExtension.class)
class OrderServiceTest {
@Mock private PaymentGateway paymentGateway;
@Mock private InventoryService inventory;
@InjectMocks private OrderService orderService;
// ...
}
// Wariant 2: MockitoAnnotations.openMocks() w try-with-resources
class OrderServiceTest {
@Mock private PaymentGateway paymentGateway;
@InjectMocks private OrderService orderService;
private AutoCloseable mocks;
@BeforeEach
void setUp() {
mocks = MockitoAnnotations.openMocks(this);
}
@AfterEach
void tearDown() throws Exception {
mocks.close(); // zwalnia zasoby (m.in. mocki inline static)
}
}
Tabela porównawcza:
| Cecha | Mockito.mock(Class) |
@Mock |
|---|---|---|
| Styl | Programatyczny | Deklaratywny |
| Wymaga inicjalizacji? | Nie | Tak (MockitoExtension / openMocks) |
| Nazwa mocka w komunikatach | Nazwa klasy (userRepository) |
Nazwa pola (repository) |
| Czytelność przy 5+ mockach | Niska (powtarzalny kod) | Wysoka |
Integracja z @InjectMocks |
Manualna | Automatyczna |
| Sprawdzanie nieużytych stubów | Tylko ręcznie | Domyślnie w MockitoExtension (STRICT) |
| Zwolnienie zasobów | Automatyczne (GC) | Wymaga close() przy openMocks |
Praktyczna rada: w JUnit 5 używaj MockitoExtension — daje strict stubbing (wykrywa nieużyte when(...)), automatyczną inicjalizację i czysty kod. openMocks z try-with-resources jest potrzebny tylko, gdy nie możesz dodać rozszerzenia.
Źródło: site.mockito.org
8. Jak działa @Spy i kiedy używać go zamiast @Mock?
@Spy tworzy częściowy mock — opakowuje realny obiekt, zachowując domyślnie wszystkie jego prawdziwe zachowania. Możesz selektywnie nadpisać wybrane metody, podczas gdy @Mock zwraca wartości domyślne (null, 0, pusta kolekcja) dla wszystkich metod.
flowchart LR
M["@Mock"] --> M1["Cała klasa zmockowana"]
M --> M2["Metody zwracają wartości domyślne<br/>(null, 0, [], false)"]
M --> M3["Zachowanie musisz<br/>zdefiniować przez when(...)"]
S["@Spy"] --> S1["Realny obiekt opakowany"]
S --> S2["Domyślnie używa prawdziwego<br/>kodu klasy"]
S --> S3["Selektywnie nadpisujesz<br/>wybrane metody"]
Przykład:
@ExtendWith(MockitoExtension.class)
class CartTest {
@Spy
private ShippingCalculator shipping = new ShippingCalculator();
@Mock
private DiscountService discountService;
@InjectMocks
private Cart cart;
@Test
void shouldUseRealShippingButMockedDiscount() {
// Realna metoda obliczy koszt
BigDecimal cost = shipping.calculate(5);
// Selektywnie nadpisz tylko jedną metodę
doReturn(BigDecimal.ZERO).when(shipping).calculate(0);
when(discountService.getDiscount(any())).thenReturn(0.1);
cart.checkout();
// Weryfikacja działa identycznie jak dla @Mock
verify(shipping).calculate(anyInt());
}
}
Tabela porównawcza:
| Aspekt | @Mock |
@Spy |
|---|---|---|
| Tworzony obiekt | Pełny mock | Wrapper na realny obiekt |
| Zachowanie domyślne metod | Wartości domyślne (null, 0, ...) |
Prawdziwa implementacja klasy |
| Wymaga realnej instancji? | Nie | Tak (lub konstruktora bezargumentowego) |
| Składnia stubowania | when(mock.foo()).thenReturn(x) |
doReturn(x).when(spy).foo() (preferowane) |
| Typowe użycie | Zależności (repozytoria, usługi) | Klasy legacy, częściowe zastępowanie |
| Ryzyko | Brak — wszystko jest fałszywe | Wywołanie prawdziwego kodu z side-effectami |
Kiedy używać @Spy:
- Testujesz klasę legacy, której nie da się łatwo rozdzielić na komponenty.
- Chcesz przetestować realne zachowanie obiektu, ale podmienić jedną-dwie metody (np. zwracającą czas systemowy).
- Pracujesz z klasą, w której większość metod ma sens w teście, ale jedna woła zewnętrzny system.
Pułapka: przy @Spy używaj doReturn(...).when(spy).method() zamiast when(spy.method()).thenReturn(...) — ta druga forma najpierw rzeczywiście wywołuje metodę szpiega, co przy side-effectach lub wyjątkach może zepsuć test.
Źródło: site.mockito.org
9. Czym jest @InjectMocks i jakie strategie wstrzykiwania obsługuje (constructor/setter/field)?
@InjectMocks instruuje Mockito, aby utworzyło instancję testowanej klasy i wstrzyknęło do niej mocki oznaczone @Mock lub @Spy. Mockito próbuje trzech strategii w określonej kolejności — pierwsza, która zadziała, jest używana.
flowchart TD
Start([Mockito napotyka @InjectMocks]) --> C{1. Constructor injection<br/>czy istnieje konstruktor,<br/>do którego pasują mocki?}
C -->|Tak| C1[Wybierz konstruktor o największej<br/>liczbie pasujących parametrów<br/>i wywołaj go z mockami]
C -->|Nie| S{2. Setter injection<br/>czy obiekt ma settery<br/>dla pól typu mocków?}
S -->|Tak| S1[Utwórz instancję bezargumentowym<br/>konstruktorem i wywołaj settery]
S -->|Nie| F{3. Field injection<br/>czy są pola<br/>tego samego typu?}
F -->|Tak| F1[Utwórz instancję bezargumentowym<br/>konstruktorem i ustaw pola<br/>przez refleksję]
F -->|Nie| NULL[Pole pozostaje NULL<br/>NPE w teście]
C1 --> End([Mocki wstrzyknięte])
S1 --> End
F1 --> End
Przykład — wszystkie trzy strategie:
// Klasa testowana ma konstruktor → constructor injection
public class OrderService {
private final PaymentGateway gateway;
private final InventoryService inventory;
public OrderService(PaymentGateway gateway, InventoryService inventory) {
this.gateway = gateway;
this.inventory = inventory;
}
}
@ExtendWith(MockitoExtension.class)
class OrderServiceTest {
@Mock private PaymentGateway gateway;
@Mock private InventoryService inventory;
@InjectMocks private OrderService orderService; // → constructor injection
}
// Klasa testowana ma settery → setter injection
public class ReportGenerator {
private DataSource dataSource;
public ReportGenerator() {}
public void setDataSource(DataSource ds) { this.dataSource = ds; }
}
@InjectMocks private ReportGenerator generator; // → setter injection
// Klasa testowana ma tylko pola → field injection (przez refleksję)
public class Notifier {
@Inject private EmailClient emailClient;
@Inject private SmsClient smsClient;
}
@InjectMocks private Notifier notifier; // → field injection
Najważniejsze pułapki:
- NULL field gdy żadna strategia nie pasuje — najczęstszy problem. Jeśli klasa ma konstruktor z parametrami, do których Mockito nie potrafi dopasować mocków (np. brakuje mocka, typy nie pasują), i równocześnie nie ma konstruktora bezargumentowego ani setterów, pole pozostaje
nulli pierwszyverify(...)rzucaNullPointerException. - Wyjątek z konstruktora — gdy Mockito wybierze constructor injection, ale konstruktor rzuci wyjątek (np. walidacja parametrów), test wybuchnie na etapie inicjalizacji.
- Final fields tylko przez constructor — pola
finalnie da się ustawić ani przez setter, ani przez field injection. - Niejednoznaczne dopasowanie — gdy w klasie są dwa pola tego samego typu, Mockito dopasowuje po nazwie pola (musi odpowiadać nazwie mocka).
@InjectMocksNIE wywoła@PostConstructani logiki kontenera DI — to czysta refleksja Mockito.
Praktyczna rada: preferuj constructor injection — czyni zależności jawnymi, pozwala używać final, i jeśli czegoś brakuje, kod się nie skompiluje (zamiast zawieść w runtime).
Źródło: site.mockito.org
10. Jak mockować obiekty z parametrami generycznymi (np. List<String>)?
Z powodu type erasure w Javie typ generyczny List<String> w czasie wykonania jest zwykłym List. Mockito nie zna parametru generycznego, dlatego tworzenie mocka generycznego wymaga albo adnotacji @Mock, albo manualnego rzutowania.
flowchart LR
A["Tworzenie mocka List<String>"] --> B{Sposób}
B --> P["Mockito.mock(List.class)<br/>(programatyczny)"]
P --> P1["Zwraca surowy List"]
P --> P2["Wymaga rzutowania<br/>(List<String>) mock(List.class)"]
P --> P3["Kompilator: WARNING<br/>unchecked cast"]
P --> P4["@SuppressWarnings(unchecked)"]
B --> D["@Mock private List<String> names<br/>(deklaratywny)"]
D --> D1["Mockito używa typu pola"]
D --> D2["Brak rzutowania w kodzie"]
D --> D3["Brak warningu"]
D --> D4["Czystsze, zalecane"]
Sposób 1: @Mock (zalecany — bez warningu)
@ExtendWith(MockitoExtension.class)
class TagServiceTest {
@Mock
private List<String> names;
@Mock
private Map<Long, User> userCache;
@Test
void shouldReturnTagsForUser() {
when(names.get(0)).thenReturn("Anna");
when(names.size()).thenReturn(3);
assertEquals("Anna", names.get(0));
assertEquals(3, names.size());
}
}
Sposób 2: Mockito.mock() z rzutowaniem (warning unchecked)
class TagServiceTest {
@Test
@SuppressWarnings("unchecked") // konieczne — kompilator nie zna typu generycznego
void shouldMockGenericList() {
// Wymaga jawnego rzutowania
List<String> names = (List<String>) mock(List.class);
when(names.get(0)).thenReturn("Anna");
assertEquals("Anna", names.get(0));
}
}
Sposób 3: Wskazówka typu przez mock() z withSettings (nadal wymaga rzutowania)
@SuppressWarnings("unchecked")
List<String> names = mock(List.class, withSettings().name("namesList"));
Argumenty generyczne w stubowaniu — wskazówki typowe:
@Mock private Repository<User> userRepository;
@Test
void shouldHandleGenerics() {
// Bez wskazówki typu — kompilator może mieć problem z inferencją
when(userRepository.findAll()).thenReturn(List.of(new User("Anna")));
// Dla matcherów argumentów używaj ArgumentMatchers.<T>any()
when(userRepository.save(ArgumentMatchers.<User>any()))
.thenAnswer(inv -> inv.getArgument(0));
// Dla ArgumentCaptor — kapturuj w surowym typie lub forCaptor()
@SuppressWarnings("unchecked")
ArgumentCaptor<List<String>> captor = ArgumentCaptor.forClass(List.class);
}
Najważniejsze fakty:
- Type erasure powoduje, że
mock(List.class)zwraca surowy typ — kompilator zgłasza unchecked warning przy rzutowaniu. @Mockjest czystszy — Mockito odczytuje typ pola przez refleksję i nie wymaga adnotacji@SuppressWarnings.- Dla
ArgumentCaptorz typem generycznym (np.ArgumentCaptor<List<String>>) używajArgumentCaptor.forClass(List.class)z@SuppressWarnings("unchecked")lub od Mockito 3.4+ArgumentCaptor.captor()(Java 8+, wnioskuje typ z miejsca wywołania). - W matcherach typu generycznego stosuj
ArgumentMatchers.<T>any(), jeśli kompilator nie potrafi wywnioskować typu.
Źródło: site.mockito.org
Stubbing - definiowanie zachowania mocków
11. Jak stubować metody za pomocą when().thenReturn() i doReturn().when()?
Stubowanie to definiowanie zachowania mocka — co ma zwrócić, gdy zostanie wywołana konkretna metoda z konkretnymi argumentami. Mockito oferuje dwie podstawowe składnie: BDD-style when().thenReturn() oraz doReturn().when().
Składnia when().thenReturn() — najbardziej czytelna, używana w 90% przypadków:
import static org.mockito.Mockito.*;
@Test
void whenThenReturn_basicUsage() {
UserRepository repo = mock(UserRepository.class);
// stubowanie: gdy findById(1L) → zwróć Optional.of(user)
when(repo.findById(1L)).thenReturn(Optional.of(new User("Anna")));
when(repo.findById(2L)).thenReturn(Optional.empty());
assertEquals("Anna", repo.findById(1L).get().getName());
assertTrue(repo.findById(2L).isEmpty());
// niezestubowane wywołanie zwraca default (null/empty/0/false)
assertTrue(repo.findById(99L).isEmpty());
}
Składnia doReturn().when() — alternatywna kolejność wywołań, niezbędna w specyficznych przypadkach (void, spy, final):
@Test
void doReturnWhen_basicUsage() {
UserRepository repo = mock(UserRepository.class);
// składnia odwrócona: NAJPIERW doReturn, potem when(mock).method()
doReturn(Optional.of(new User("Bob"))).when(repo).findById(1L);
assertEquals("Bob", repo.findById(1L).get().getName());
}
Kluczowa różnica techniczna: when(repo.findById(1L)) faktycznie wywołuje metodę findById(1L) na mocku, żeby Mockito mogło zarejestrować, którą metodę stubujesz. Dla zwykłego mocka jest to bezpieczne (mock zwraca default), ale dla spy lub metod void to wywołanie byłoby problematyczne. doReturn(...).when(repo) najpierw zwraca specjalny stub, a dopiero potem rejestruje wywołanie — nie wykonuje realnej logiki.
Matchery argumentów — kiedy nie chcesz dopasować dokładnej wartości:
when(repo.findById(anyLong())).thenReturn(Optional.of(defaultUser));
when(service.process(eq("ADMIN"), anyInt())).thenReturn("OK");
// UWAGA: gdy używasz JEDNEGO matchera, WSZYSTKIE argumenty muszą być matcherami
Łańcuchowanie — kolejne zachowania można łączyć:
when(repo.count())
.thenReturn(0L) // pierwsze wywołanie
.thenReturn(1L) // drugie
.thenThrow(new RuntimeException("boom")); // trzecie i kolejne
Typowe scenariusze użycia:
when().thenReturn()— domyślny wybór dla mocków, czytelny w testach BDD,doReturn().when()— dla metod void, spy, metod final, gdy realne wywołanie miałoby skutki uboczne,thenAnswer()— gdy zwracana wartość zależy od argumentów (lambda otrzymujeInvocationOnMock),thenCallRealMethod()— wywołaj prawdziwą implementację (rzadko, głównie przy częściowych mockach).
Źródła: site.mockito.org, javadoc.io/Mockito.
12. Kiedy używać when() a kiedy doReturn() - jakie są pułapki z metodami void i spy?
Wybór między when().thenReturn() a doReturn().when() nie jest tylko kwestią stylu — w niektórych sytuacjach when() po prostu nie zadziała lub spowoduje wykonanie prawdziwej logiki, czego absolutnie nie chcesz w teście.
Porównanie obu podejść:
flowchart TB
Start[Stubowanie metody]
Start --> Q1{Czy to spy<br/>partial mock?}
Q1 -->|Tak| DoReturn1[Użyj doReturn().when()<br/>wywołanie when wykona REAL method]
Q1 -->|Nie| Q2{Czy metoda<br/>jest void?}
Q2 -->|Tak| DoReturn2[Użyj doThrow/doNothing/doAnswer<br/>when() nie skompiluje się dla void]
Q2 -->|Nie| Q3{Czy potrzebujesz<br/>sekwencji wyjątków<br/>na metodach void?}
Q3 -->|Tak| DoReturn3[Użyj doThrow().doNothing().when()]
Q3 -->|Nie| WhenThen[Użyj when().thenReturn()<br/>czytelny BDD-style]
style DoReturn1 fill:#ffcccc
style DoReturn2 fill:#ffcccc
style DoReturn3 fill:#ffe0cc
style WhenThen fill:#ccffcc
Pułapka #1 — when() na spy wywołuje prawdziwą metodę. To najczęściej spotykany problem:
List<String> realList = new ArrayList<>();
List<String> spyList = spy(realList);
// PUŁAPKA: spyList.get(0) JEST wykonywane przed when() i rzuca IndexOutOfBoundsException!
when(spyList.get(0)).thenReturn("stubbed"); // ← wybuch
// POPRAWNIE: doReturn nie wywołuje get(0) na prawdziwym obiekcie
doReturn("stubbed").when(spyList).get(0);
assertEquals("stubbed", spyList.get(0));
Dla zwykłych mocków when(mock.get(0)) jest bezpieczne, bo mock zwraca null (default). Ale spy opakowuje realny obiekt — i get(0) na pustej liście naprawdę rzuca wyjątek, zanim Mockito zdąży zarejestrować stub.
Pułapka #2 — metody void nie kompilują się z when(). Konstrukcja when(mock.voidMethod()) nie skompiluje się, bo void nie jest wyrażeniem. Trzeba użyć:
EmailService service = mock(EmailService.class);
// metoda void send(String) — nie da się zrobić when(service.send("..."))
doNothing().when(service).send("welcome@example.com"); // domyślne dla mocka, ale jawnie
doThrow(new MailException()).when(service).send("invalid");
service.send("welcome@example.com"); // OK
assertThrows(MailException.class,
() -> service.send("invalid"));
Pułapka #3 — sekwencje zachowań na metodach void. Łańcuchowanie do* działa tak samo jak thenReturn:
doNothing()
.doThrow(new RuntimeException("second call fails"))
.doNothing()
.when(service).send(anyString());
service.send("a"); // OK (doNothing)
assertThrows(RuntimeException.class, () -> service.send("b")); // wyjątek
service.send("c"); // OK znów
Pułapka #4 — metody final i static. W Mockito ≥ 3.4 (z mockito-inline) można mockować final i static, ale przy spy partial-mock metod final zalecane jest doReturn().
Reguła praktyczna:
- Mock + metoda zwracająca wartość →
when().thenReturn()(czytelność BDD), - Mock + metoda void →
doNothing()/doThrow()/doAnswer(), - Spy (cokolwiek) → zawsze
doReturn()/doThrow(), - Metoda zwraca skomplikowany typ generyczny →
doReturn()(czasem omija problemy z type inference).
Źródła: site.mockito.org — Mockito.spy(), javadoc.io — doReturn.
13. Jak stubować metody rzucające wyjątki (thenThrow, doThrow)?
Testowanie ścieżek błędu wymaga symulowania wyjątków. Mockito udostępnia thenThrow() (dla metod zwracających wartość) oraz doThrow() (dla metod void i spy).
thenThrow() — metody zwracające wartość:
import static org.mockito.Mockito.*;
@Test
void thenThrow_basicUsage() {
UserRepository repo = mock(UserRepository.class);
// rzuć wyjątek po podaniu konkretnego argumentu
when(repo.findById(999L))
.thenThrow(new EntityNotFoundException("user 999"));
// rzuć dla dowolnego argumentu
when(repo.findByEmail(anyString()))
.thenThrow(new RuntimeException("DB down"));
assertThrows(EntityNotFoundException.class,
() -> repo.findById(999L));
}
Możesz podać klasę wyjątku zamiast instancji — Mockito utworzy go z konstruktora bezargumentowego:
when(repo.findById(999L)).thenThrow(EntityNotFoundException.class);
doThrow() — metody void:
@Test
void doThrow_voidMethod() {
EmailService service = mock(EmailService.class);
// dla void NIE MOŻNA użyć when() — używamy doThrow().when().method()
doThrow(new MailException("SMTP down"))
.when(service).send("user@example.com");
assertThrows(MailException.class,
() -> service.send("user@example.com"));
// inne argumenty nie rzucają — wciąż domyślne doNothing
service.send("other@example.com"); // OK
}
Łączenie zachowań — sukces, błąd, znów sukces:
// metoda zwracająca wartość
when(repo.count())
.thenReturn(0L)
.thenThrow(new SQLException("timeout"))
.thenReturn(42L);
// metoda void
doNothing()
.doThrow(new RuntimeException("retry"))
.doNothing()
.when(service).send(anyString());
Wyjątki sprawdzane (checked exceptions) — Mockito sprawdza, czy deklarowana sygnatura metody pozwala na wyrzucenie danego typu. Jeśli void send(String) deklaruje throws MailException, możesz wyrzucić MailException. Próba wyrzucenia checked exception, którego metoda nie deklaruje, spowoduje błąd Mockito:
// jeśli metoda nie deklaruje throws IOException — błąd!
doThrow(new IOException()).when(service).send("x");
// rozwiązanie: użyj RuntimeException lub jego podklasy
doThrow(new UncheckedIOException("io fail", new IOException()))
.when(service).send("x");
Typowe zastosowania:
- Symulowanie awarii zewnętrznych zależności (DB, HTTP, message broker),
- Testowanie obsługi błędów w warstwie serwisowej (try/catch, retry, fallback),
- Wymuszanie ścieżki "exception path" w testach jednostkowych,
- Symulowanie nieprzewidywalnych wyjątków od stron trzecich (
OptimisticLockException,DataAccessException).
Spy + wyjątek — zawsze przez doThrow():
List<String> spyList = spy(new ArrayList<>());
doThrow(new IllegalStateException()).when(spyList).clear();
assertThrows(IllegalStateException.class, spyList::clear);
14. Jak działają kolejne wywołania (thenReturn(a, b, c)) i kiedy je stosować?
Mockito pozwala zdefiniować różne wartości zwracane dla kolejnych wywołań tej samej metody. Jest to przydatne, gdy testujesz logikę z retry, paginacją, polling, lub stanem zmieniającym się w czasie.
Składnia varargs — thenReturn(a, b, c):
import static org.mockito.Mockito.*;
@Test
void consecutiveCalls_varargs() {
PollingService poller = mock(PollingService.class);
when(poller.status())
.thenReturn("PENDING", "PENDING", "READY");
assertEquals("PENDING", poller.status()); // 1. wywołanie
assertEquals("PENDING", poller.status()); // 2. wywołanie
assertEquals("READY", poller.status()); // 3. wywołanie
assertEquals("READY", poller.status()); // 4. i kolejne — STICKY LAST
}
Kluczowa zasada — "sticky last value": po wyczerpaniu listy Mockito powtarza ostatnią wartość w nieskończoność. To zachowanie jest dokumentowane i zamierzone — chroni przed IndexOutOfBoundsException w testach z większą liczbą wywołań niż się spodziewano.
flowchart LR
C1[1. wywołanie] -->|zwraca| A[wartość a]
C2[2. wywołanie] -->|zwraca| B[wartość b]
C3[3. wywołanie] -->|zwraca| Cv[wartość c]
C4[4. wywołanie] -->|zwraca| Cv
C5[5. wywołanie] -->|zwraca| Cv
Cn[N-te wywołanie<br/>n ≥ 3] -->|zawsze zwraca| Cv
style Cv fill:#cce5ff,stroke:#0066cc,stroke-width:2px
style C4 fill:#fff4cc
style C5 fill:#fff4cc
style Cn fill:#fff4cc
Składnia łańcuchowa — równoważna:
when(poller.status())
.thenReturn("PENDING")
.thenReturn("PENDING")
.thenReturn("READY");
Mieszanie zachowań — wartość + wyjątek:
when(api.fetch())
.thenReturn(response1)
.thenReturn(response2)
.thenThrow(new TimeoutException("3rd call failed"))
.thenReturn(response3); // 4. wywołanie znów OK
api.fetch(); // response1
api.fetch(); // response2
assertThrows(TimeoutException.class, () -> api.fetch()); // 3.
api.fetch(); // response3 (sticky)
Generyki i varargs — pułapka kompilatora:
// PROBLEM: varargs z Optional<User> może źle się zdedukować
when(repo.findById(1L)).thenReturn(Optional.empty(), Optional.of(user));
// czasem kompilator zwraca błąd "incompatible types"
// ROZWIĄZANIE: explicit cast lub łańcuch
when(repo.findById(1L))
.thenReturn(Optional.empty())
.thenReturn(Optional.of(user));
Kiedy stosować kolejne wywołania:
- Retry / circuit breaker — pierwsze N wywołań rzuca wyjątek, kolejne zwracają sukces:
when(api.call()) .thenThrow(new TimeoutException()) .thenThrow(new TimeoutException()) .thenReturn(successResponse); - Polling / status checking — symulacja stanu zmieniającego się w czasie (PENDING → READY),
- Paginacja — kolejne strony wyników:
when(repo.findPage(any())).thenReturn(page1, page2, emptyPage); - Cache miss → hit — pierwsze wywołanie zwraca
null/empty, kolejne zwracają wartość, - Token refresh — pierwszy token wygasa, drugi działa.
Alternatywa — thenAnswer() dla bardziej złożonych scenariuszy:
AtomicInteger counter = new AtomicInteger();
when(api.fetch()).thenAnswer(inv -> {
int n = counter.incrementAndGet();
return n < 3 ? null : "value";
});
Źródła: site.mockito.org, javadoc.io — Stubbing consecutive calls.
15. Jak stubować metody asynchroniczne i metody zwracające CompletableFuture?
Coraz więcej API w Javie zwraca CompletableFuture<T> (REST clients, reactive integrations, async repositories). Mockito nie wymaga żadnego specjalnego API — wystarczy zwrócić odpowiednią instancję CompletableFuture.
Zwracanie ukończonego future — sukces:
import java.util.concurrent.CompletableFuture;
import static org.mockito.Mockito.*;
@Test
void completedFuture_success() throws Exception {
AsyncUserService service = mock(AsyncUserService.class);
when(service.findByIdAsync(1L))
.thenReturn(CompletableFuture.completedFuture(new User("Anna")));
User user = service.findByIdAsync(1L).get(); // .get() blokuje do zakończenia
assertEquals("Anna", user.getName());
}
Zwracanie future zakończonego wyjątkiem:
@Test
void failedFuture() {
AsyncUserService service = mock(AsyncUserService.class);
when(service.findByIdAsync(999L))
.thenReturn(CompletableFuture.failedFuture(
new EntityNotFoundException("user 999")));
assertThrows(ExecutionException.class,
() -> service.findByIdAsync(999L).get());
// ExecutionException opakowuje oryginalny EntityNotFoundException
}
Dla Javy < 9, gdzie nie ma CompletableFuture.failedFuture():
CompletableFuture<User> failed = new CompletableFuture<>();
failed.completeExceptionally(new EntityNotFoundException("user 999"));
when(service.findByIdAsync(999L)).thenReturn(failed);
Dynamiczne stubowanie przez thenAnswer() — gdy odpowiedź zależy od argumentów:
when(service.findByIdAsync(anyLong())).thenAnswer(invocation -> {
Long id = invocation.getArgument(0);
if (id < 0) {
return CompletableFuture.failedFuture(
new IllegalArgumentException("negative id"));
}
return CompletableFuture.completedFuture(new User("user-" + id));
});
assertEquals("user-5", service.findByIdAsync(5L).get().getName());
assertThrows(ExecutionException.class,
() -> service.findByIdAsync(-1L).get());
Symulacja opóźnienia (delay) — przydatne dla testów timeoutów:
when(service.findByIdAsync(1L)).thenAnswer(inv ->
CompletableFuture.supplyAsync(() -> {
try { Thread.sleep(500); } catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
return new User("slow");
})
);
CompletableFuture<User> future = service.findByIdAsync(1L);
// teraz możesz testować timeout, orTimeout, anyOf itp.
assertThrows(TimeoutException.class,
() -> future.get(100, TimeUnit.MILLISECONDS));
Stubowanie reaktywnych typów (Mono/Flux z Project Reactor):
when(reactiveRepo.findById(1L))
.thenReturn(Mono.just(new User("Anna")));
when(reactiveRepo.findAll())
.thenReturn(Flux.just(user1, user2, user3));
when(reactiveRepo.findById(999L))
.thenReturn(Mono.error(new EntityNotFoundException()));
Stubowanie Future<T> (klasyczny java.util.concurrent.Future):
ExecutorService executor = mock(ExecutorService.class);
Future<String> fakeFuture = CompletableFuture.completedFuture("done");
when(executor.submit(any(Callable.class))).thenReturn(fakeFuture);
Praktyczne zasady:
- Najczęstszy przypadek →
thenReturn(CompletableFuture.completedFuture(value))— synchroniczna odpowiedź, future natychmiast gotowy, idealny dla testów logiki biznesowej. - Test obsługi błędów →
CompletableFuture.failedFuture(exception)lubcompleteExceptionally(). - Test concurrency / timeouts →
supplyAsync(...)zThread.sleep()lub własnymExecutor. - Test pełnego pipeline'u async → rozważ użycie
CompletableFuture.runAsync(...)z kontrolowanym executorem (np. single-thread) — łatwiej zweryfikować kolejność. - Unikaj
Thread.sleep()w testach — preferujAwaitility(await().until(...)) do oczekiwania na zakończenie future.
Verify dla metod async:
service.findByIdAsync(1L);
service.findByIdAsync(2L);
verify(service).findByIdAsync(1L);
verify(service).findByIdAsync(2L);
verify(service, times(2)).findByIdAsync(anyLong());
Źródła: site.mockito.org, javadoc.io — Mockito.thenAnswer, docs.oracle.com — CompletableFuture.
Argument matchers
16. Czym są argument matchers w Mockito (any(), eq(), argThat())?
Argument matchers to specjalne metody Mockito, które pozwalają dopasować argumenty wywołania metody według wzorca, a nie według konkretnej wartości. Używamy ich, gdy nie znamy lub nie chcemy specyfikować dokładnej wartości argumentu przekazywanego do mockowanej metody. Matchers wykorzystują wewnętrznie mechanizm Hamcrest i działają w trybie ArgumentMatchers (statycznym imporcie).
Najpopularniejsze matchery:
import static org.mockito.ArgumentMatchers.*;
// any() - pasuje do dowolnego argumentu (również null)
when(userService.findById(any())).thenReturn(user);
// any(Class) - pasuje do dowolnego argumentu danego typu
when(userService.save(any(User.class))).thenReturn(savedUser);
// anyString(), anyInt(), anyLong() - typowane matchery (nie null!)
when(repo.findByName(anyString())).thenReturn(user);
when(repo.findById(anyLong())).thenReturn(user);
// eq() - pasuje do konkretnej wartości (używany gdy MIESZAMY z innymi matcherami)
when(service.transfer(eq(100), anyString())).thenReturn(true);
// argThat() - własna logika dopasowania (lambda lub klasa)
when(repo.save(argThat(u -> u.getAge() > 18))).thenReturn(savedUser);
// isNull(), notNull(), isA(Class) - pomocnicze matchery
when(service.process(isNull())).thenThrow(new IllegalArgumentException());
graph LR
A[Argument Matchers] --> B[any]
A --> C[anyString]
A --> D[anyInt/Long]
A --> E[eq value]
A --> F[argThat custom]
A --> G[isNull/notNull]
B -->|dowolny obiekt + null| H[Object/Generic]
C -->|dowolny String, nie null| I[String]
D -->|dowolna liczba| J[primitive]
E -->|konkretna wartość| K[literal]
F -->|własna logika lambda| L[Predicate]
G -->|null check| M[null/not-null]
Matchery upraszczają stubbing i weryfikację, gdy testujemy zachowanie, a nie dokładną wartość argumentu - np. weryfikujemy, że metoda została wywołana z jakimkolwiek użytkownikiem, bez konieczności konstruowania identycznego obiektu.
17. Dlaczego nie można mieszać surowych wartości z matcherami w jednym wywołaniu?
To kluczowa zasada Mockito: jeśli używasz choćby jednego argument matchera w wywołaniu metody, WSZYSTKIE pozostałe argumenty muszą również być matcherami. Mieszanie surowych wartości (literali) z matcherami powoduje błąd InvalidUseOfMatchersException.
Dlaczego tak się dzieje technicznie?
Matchery Mockito nie zwracają wartości - one rejestrują się na wewnętrznym stosie (ArgumentMatcherStorage). Gdy wywołujesz when(mock.method(anyInt(), "literal")), Mockito widzi:
- pozycja 0: matcher
anyIntze stosu - pozycja 1: literal
"literal"(brak matchera na stosie!)
Mockito nie wie, czy "literal" to argument, czy może brakuje matchera - i wyrzuca wyjątek.
Błędne użycie (wyrzuca wyjątek):
// ŹLE - miks literal + matcher
when(service.transfer(100, anyString())).thenReturn(true);
// InvalidUseOfMatchersException: 2 matchers expected, 1 recorded
Poprawne użycie - opakowanie literali w eq():
// DOBRZE - wszystko jako matchery
when(service.transfer(eq(100), anyString())).thenReturn(true);
// Werifikacja również wymaga konsekwencji
verify(service).transfer(eq(100), eq("USD"));
// LUB bez matcherów w ogóle:
verify(service).transfer(100, "USD");
Typowe rozwiązania:
// Same literale - bez matcherów
when(repo.findById(42L)).thenReturn(user);
// Wszystkie matchery
when(repo.findByNameAndAge(anyString(), anyInt())).thenReturn(user);
// Mix - literale opakowane w eq()
when(service.save(eq("Jan"), any(Address.class))).thenReturn(true);
18. Jak tworzyć własne argument matchery za pomocą ArgumentMatcher lub ArgumentMatchers.argThat?
Gdy wbudowane matchery nie wystarczają, możemy tworzyć własne matchery dwoma sposobami: za pomocą lambdy w argThat() (krótkie, jednorazowe sprawdzenia) lub implementując interfejs ArgumentMatcher<T> (wielokrotne użycie, czytelne komunikaty błędów).
Sposób 1: argThat() z lambdą (Java 8+)
Idealne do prostych, jednorazowych sprawdzeń bezpośrednio w teście:
import static org.mockito.ArgumentMatchers.argThat;
// Lambda - sprawdza długość stringa
when(repo.findByName(argThat(s -> s.length() > 5)))
.thenReturn(user);
// Lambda - sprawdza pole obiektu
when(userService.save(argThat(u -> u.getAge() >= 18 && u.getEmail() != null)))
.thenReturn(savedUser);
// Werifikacja z argThat
verify(emailService).send(argThat(msg ->
msg.getSubject().contains("Welcome") &&
msg.getTo().endsWith("@example.com")
));
Sposób 2: Implementacja ArgumentMatcher<T> - reużywalna klasa
Lepsze dla skomplikowanej logiki i wielokrotnego użycia. Dodatkowo metoda toString() poprawia czytelność komunikatów błędów:
import org.mockito.ArgumentMatcher;
class IsAdultUser implements ArgumentMatcher<User> {
@Override
public boolean matches(User user) {
return user != null && user.getAge() >= 18;
}
@Override
public String toString() {
// Komunikat w razie błędu weryfikacji
return "<adult user (age >= 18)>";
}
}
// Użycie w teście
when(repo.save(argThat(new IsAdultUser()))).thenReturn(savedUser);
verify(notificationService).notify(argThat(new IsAdultUser()));
Sposób 3: Statyczna metoda fabrykująca - najbardziej czytelnie
public class UserMatchers {
public static User adultUser() {
return argThat(u -> u != null && u.getAge() >= 18);
}
public static User userWithEmail(String email) {
return argThat(u -> email.equals(u.getEmail()));
}
}
// W teście - czytelnie jak DSL
when(repo.save(adultUser())).thenReturn(savedUser);
verify(emailService).send(userWithEmail("jan@example.com"));
Kiedy używać którego:
| Sposób | Kiedy stosować |
|---|---|
argThat(lambda) |
Proste, jednorazowe sprawdzenia w jednym teście |
ArgumentMatcher<T> klasa |
Powtarzalne wzorce, lepsze komunikaty błędów |
| Statyczne fabryki | Wspólne matchery dla wielu testów, DSL-style |
19. Jak działa ArgumentCaptor i kiedy go używać zamiast matcherów?
ArgumentCaptor to mechanizm Mockito do przechwytywania argumentów, z którymi wywołano mockowaną metodę, a następnie do wykonania na nich asercji (np. JUnit/AssertJ). W przeciwieństwie do matcherów, captor przechowuje rzeczywistą wartość i pozwala ją inspekcjonować po wywołaniu.
Filozofia użycia:
- Matcher - używamy podczas stubbingu (
when().thenReturn()) i prostej weryfikacji - Captor - używamy podczas weryfikacji gdy chcemy dokładnie sprawdzić złożony argument
sequenceDiagram
participant Test
participant Mock
participant Captor as ArgumentCaptor
Test->>Captor: ArgumentCaptor.forClass(User.class)
Test->>Mock: serviceUnderTest.register(jan)
Note over Mock: mock.save(jan) jest wywołane wewnętrznie
Test->>Mock: verify(mock).save(captor.capture())
Mock-->>Captor: zapisuje argument 'jan'
Test->>Captor: captor.getValue()
Captor-->>Test: zwraca przechwycony obiekt User
Test->>Test: assertEquals/assertThat na polach
Podstawowe użycie - tworzenie przez forClass():
import org.mockito.ArgumentCaptor;
@Test
void shouldSaveUserWithCorrectData() {
// given
UserService service = new UserService(userRepoMock);
ArgumentCaptor<User> captor = ArgumentCaptor.forClass(User.class);
// when
service.register("Jan", "jan@example.com", 25);
// then - przechwyć argument
verify(userRepoMock).save(captor.capture());
// Asercje na przechwyconej wartości
User captured = captor.getValue();
assertEquals("Jan", captured.getName());
assertEquals("jan@example.com", captured.getEmail());
assertEquals(25, captured.getAge());
assertTrue(captured.getCreatedAt().isBefore(Instant.now()));
}
Użycie z adnotacją @Captor:
@ExtendWith(MockitoExtension.class)
class UserServiceTest {
@Mock private UserRepository repo;
@Captor private ArgumentCaptor<User> userCaptor;
@Captor private ArgumentCaptor<List<String>> tagsCaptor; // działa z generykami!
@Test
void shouldCaptureMultipleInvocations() {
UserService service = new UserService(repo);
service.registerAll(List.of("Jan", "Anna", "Piotr"));
// capture() zbiera WSZYSTKIE wywołania
verify(repo, times(3)).save(userCaptor.capture());
List<User> allUsers = userCaptor.getAllValues();
assertEquals(3, allUsers.size());
assertEquals("Anna", allUsers.get(1).getName());
}
}
Captor vs Matcher - praktyczne porównanie:
// MATCHER - dobre dla prostych asercji "czy w ogóle wywołano"
verify(emailService).send(argThat(msg -> msg.getSubject().contains("Welcome")));
// CAPTOR - lepsze dla wielu asercji na tym samym obiekcie
ArgumentCaptor<Email> emailCaptor = ArgumentCaptor.forClass(Email.class);
verify(emailService).send(emailCaptor.capture());
Email sent = emailCaptor.getValue();
assertThat(sent.getSubject()).contains("Welcome");
assertThat(sent.getTo()).isEqualTo("jan@example.com");
assertThat(sent.getBody()).isNotEmpty();
assertThat(sent.getAttachments()).hasSize(2);
Kiedy używać Captora:
- Sprawdzasz wiele pól złożonego obiektu
- Chcesz porównać obiekt z czymś wygenerowanym dynamicznie (np. timestamp)
- Chcesz inspekcjonować wszystkie wywołania w pętli (
getAllValues()) - Chcesz lepsze komunikaty błędów niż przy
argThat()(AssertJ je dostarcza)
20. Jakie są typowe pułapki użycia any(Class) dla typów generycznych (warnings, NullPointerException)?
Używanie any() i any(Class) wymaga ostrożności - są trzy typowe pułapki: problem z typami generycznymi (warnings), NullPointerException na primitive types, oraz nieoczekiwane dopasowanie null.
Pułapka 1: NullPointerException przy primitive types
Metoda any() zwraca null. Gdy Mockito przekazuje null do metody, która oczekuje primitive (int, long, boolean), Java próbuje rozpakować null przez Integer.intValue() - co rzuca NullPointerException.
public interface Calculator {
int add(int a, int b); // primitive int!
}
// ŹLE - any() zwraca null, primitive int nie może być null
when(calc.add(any(), any())).thenReturn(5);
// NullPointerException przy unboxing
// DOBRZE - użyj dedykowanych matcherów dla primitive
when(calc.add(anyInt(), anyInt())).thenReturn(5);
when(repo.findById(anyLong())).thenReturn(user);
when(flag.check(anyBoolean())).thenReturn(true);
Pułapka 2: Generyki i warning unchecked
W typach generycznych any(Class) powoduje warning z powodu type erasure:
// Warning: unchecked conversion
when(service.process(any(List.class))).thenReturn(result);
// Lepiej - bezpieczny cast lub po prostu any()
when(service.process(ArgumentMatchers.<List<String>>any())).thenReturn(result);
// Najczęściej wystarczy any() - kompilator wywnioskuje typ z kontekstu
when(service.process(any())).thenReturn(result); // bez warning
Pułapka 3: any() dopasowuje również null
Od Mockito 2.0 any() dopasowuje null oraz dowolny obiekt - to różnica względem anyString(), anyList(), które NIE dopasowują null:
// any() - łapie też null!
when(repo.findByName(any())).thenReturn(user);
repo.findByName(null); // zwraca user
// anyString() - NIE łapie null
when(repo.findByName(anyString())).thenReturn(user);
repo.findByName(null); // NIE pasuje - zwraca default (null)
// Jeśli chcesz wyraźnie "non-null string"
verify(repo).findByName(anyString()); // weryfikuje wywołanie z not-null
Tabela porównawcza matcherów any*():
| Matcher | Dopasowuje | NIE dopasowuje | Kiedy używać |
|---|---|---|---|
any() |
dowolny obiekt + null |
nic | Najbardziej ogólnie, generyki |
any(MyClass.class) |
obiekty MyClass + null |
inne typy | Gdy ważny jest typ (np. polimorfizm) |
anyString() |
dowolny String (not null) | null, inne typy |
Stringi gdy null jest osobnym przypadkiem |
anyInt(), anyLong() |
dowolna liczba primitive | null |
OBOWIĄZKOWO dla primitive int/long |
anyBoolean() |
true lub false |
null |
OBOWIĄZKOWO dla primitive boolean |
anyList(), anyMap() |
kolekcje (not null) | null |
Kolekcje gdy null jest osobnym przypadkiem |
isNull() |
tylko null |
wszystko inne | Test ścieżki obsługi null |
Praktyczne wskazówki:
// 1. Primitive - ZAWSZE używaj typowanych matcherów
when(service.calculate(anyInt(), anyDouble())).thenReturn(42.0);
// 2. Wrapper types (Integer, Long) - any() jest OK
when(service.calculate(any(Integer.class), any(Double.class))).thenReturn(42.0);
// 3. Generyki - any() wystarczy, typ z kontekstu
when(repo.<User>findByPredicate(any())).thenReturn(user);
// 4. Jeśli chcesz wymusić not-null
when(repo.save(notNull())).thenReturn(saved);
Weryfikacja interakcji
verify(), times(), never())?
Odpowiedź dostępna w pełnej wersji. Odblokuj 35 pozostałych odpowiedzi i ucz się z pełnego zestawu.
Odblokuj odpowiedziatLeast, atMost, atLeastOnce, atMostOnce?
Odpowiedź dostępna w pełnej wersji. Odblokuj 35 pozostałych odpowiedzi i ucz się z pełnego zestawu.
Odblokuj odpowiedziInOrder?
Odpowiedź dostępna w pełnej wersji. Odblokuj 35 pozostałych odpowiedzi i ucz się z pełnego zestawu.
Odblokuj odpowiedziverifyNoInteractions i verifyNoMoreInteractions?
Odpowiedź dostępna w pełnej wersji. Odblokuj 35 pozostałych odpowiedzi i ucz się z pełnego zestawu.
Odblokuj odpowiedziOdpowiedź dostępna w pełnej wersji. Odblokuj 35 pozostałych odpowiedzi i ucz się z pełnego zestawu.
Odblokuj odpowiedziSpy - częściowe mocki
@Spy i jak różni się od @Mock (real method calls vs mock behavior)?
Odpowiedź dostępna w pełnej wersji. Odblokuj 35 pozostałych odpowiedzi i ucz się z pełnego zestawu.
Odblokuj odpowiedzi@Spy zamiast @Mock (legacy code, refactoring)?
Odpowiedź dostępna w pełnej wersji. Odblokuj 35 pozostałych odpowiedzi i ucz się z pełnego zestawu.
Odblokuj odpowiedzi@Spy (real method calls, side effects, NullPointerException)?
Odpowiedź dostępna w pełnej wersji. Odblokuj 35 pozostałych odpowiedzi i ucz się z pełnego zestawu.
Odblokuj odpowiedzidoReturn().when())?
Odpowiedź dostępna w pełnej wersji. Odblokuj 35 pozostałych odpowiedzi i ucz się z pełnego zestawu.
Odblokuj odpowiedziKonfiguracja i resetowanie
Mockito.reset() i kiedy tego unikać?
Odpowiedź dostępna w pełnej wersji. Odblokuj 35 pozostałych odpowiedzi i ucz się z pełnego zestawu.
Odblokuj odpowiedziMockSettings i jak konfigurować mocki (withSettings())?
Odpowiedź dostępna w pełnej wersji. Odblokuj 35 pozostałych odpowiedzi i ucz się z pełnego zestawu.
Odblokuj odpowiedziAnswers.RETURNS_SMART_NULLS, RETURNS_DEEP_STUBS)?
Odpowiedź dostępna w pełnej wersji. Odblokuj 35 pozostałych odpowiedzi i ucz się z pełnego zestawu.
Odblokuj odpowiedziMockingDetails i kiedy się przydaje (debugowanie testów)?
Odpowiedź dostępna w pełnej wersji. Odblokuj 35 pozostałych odpowiedzi i ucz się z pełnego zestawu.
Odblokuj odpowiedziStatic i final methods (Mockito 3+)
mockStatic)?
Odpowiedź dostępna w pełnej wersji. Odblokuj 35 pozostałych odpowiedzi i ucz się z pełnego zestawu.
Odblokuj odpowiedzimockConstruction do mockowania new instances?
Odpowiedź dostępna w pełnej wersji. Odblokuj 35 pozostałych odpowiedzi i ucz się z pełnego zestawu.
Odblokuj odpowiedziOdpowiedź dostępna w pełnej wersji. Odblokuj 35 pozostałych odpowiedzi i ucz się z pełnego zestawu.
Odblokuj odpowiedziOdpowiedź dostępna w pełnej wersji. Odblokuj 35 pozostałych odpowiedzi i ucz się z pełnego zestawu.
Odblokuj odpowiedziIntegracja z JUnit i Spring
@ExtendWith(MockitoExtension.class))?
Odpowiedź dostępna w pełnej wersji. Odblokuj 35 pozostałych odpowiedzi i ucz się z pełnego zestawu.
Odblokuj odpowiedziMockitoAnnotations.openMocks(this) od MockitoExtension?
Odpowiedź dostępna w pełnej wersji. Odblokuj 35 pozostałych odpowiedzi i ucz się z pełnego zestawu.
Odblokuj odpowiedzi@MockBean w Spring Boot Test i jak różni się od @Mock?
Odpowiedź dostępna w pełnej wersji. Odblokuj 35 pozostałych odpowiedzi i ucz się z pełnego zestawu.
Odblokuj odpowiedzi@SpyBean i kiedy go używać zamiast @MockBean?
Odpowiedź dostępna w pełnej wersji. Odblokuj 35 pozostałych odpowiedzi i ucz się z pełnego zestawu.
Odblokuj odpowiedziOdpowiedź dostępna w pełnej wersji. Odblokuj 35 pozostałych odpowiedzi i ucz się z pełnego zestawu.
Odblokuj odpowiedziWzorce i najlepsze praktyki
Odpowiedź dostępna w pełnej wersji. Odblokuj 35 pozostałych odpowiedzi i ucz się z pełnego zestawu.
Odblokuj odpowiedzigiven/when/then) i jakie ma zalety?
Odpowiedź dostępna w pełnej wersji. Odblokuj 35 pozostałych odpowiedzi i ucz się z pełnego zestawu.
Odblokuj odpowiedziOdpowiedź dostępna w pełnej wersji. Odblokuj 35 pozostałych odpowiedzi i ucz się z pełnego zestawu.
Odblokuj odpowiedziOdpowiedź dostępna w pełnej wersji. Odblokuj 35 pozostałych odpowiedzi i ucz się z pełnego zestawu.
Odblokuj odpowiedziOdpowiedź dostępna w pełnej wersji. Odblokuj 35 pozostałych odpowiedzi i ucz się z pełnego zestawu.
Odblokuj odpowiedziPułapki i debugowanie
UnfinishedStubbingException, MissingMethodInvocationException)?
Odpowiedź dostępna w pełnej wersji. Odblokuj 35 pozostałych odpowiedzi i ucz się z pełnego zestawu.
Odblokuj odpowiedziOdpowiedź dostępna w pełnej wersji. Odblokuj 35 pozostałych odpowiedzi i ucz się z pełnego zestawu.
Odblokuj odpowiedziOdpowiedź dostępna w pełnej wersji. Odblokuj 35 pozostałych odpowiedzi i ucz się z pełnego zestawu.
Odblokuj odpowiedziStrictStubbing i jakie wymusza zasady?
Odpowiedź dostępna w pełnej wersji. Odblokuj 35 pozostałych odpowiedzi i ucz się z pełnego zestawu.
Odblokuj odpowiedziMigracja i kompatybilność
Odpowiedź dostępna w pełnej wersji. Odblokuj 35 pozostałych odpowiedzi i ucz się z pełnego zestawu.
Odblokuj odpowiedziOdpowiedź dostępna w pełnej wersji. Odblokuj 35 pozostałych odpowiedzi i ucz się z pełnego zestawu.
Odblokuj odpowiedziOdpowiedź dostępna w pełnej wersji. Odblokuj 35 pozostałych odpowiedzi i ucz się z pełnego zestawu.
Odblokuj odpowiedziOdpowiedź dostępna w pełnej wersji. Odblokuj 35 pozostałych odpowiedzi i ucz się z pełnego zestawu.
Odblokuj odpowiedzi