To jest darmowy podgląd

To jest próbka 15 pytań z naszej pełnej kolekcji 55 pytań rekrutacyjnych. Uzyskaj pełny dostęp do wszystkich pytań ze szczegółowymi odpowiedziami i przykładami kodu.

Zobacz plany cenowe

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

↑ Powrót na górę

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

↑ Powrót na górę

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

↑ Powrót na górę

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-core wystarcza.
  • 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

↑ Powrót na górę

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 otrzymuje InvocationOnMock),
  • thenCallRealMethod() — wywołaj prawdziwą implementację (rzadko, głównie przy częściowych mockach).

Źródła: site.mockito.org, javadoc.io/Mockito.

↑ Powrót na górę

Weryfikacja interakcji

21. Jak weryfikować że metoda została wywołana (verify(), times(), never())?

Weryfikacja interakcji to drugi filar Mockito (obok stubowania) — pozwala potwierdzić, że testowany kod rzeczywiście wywołał określone metody na mockach, z określoną liczbą powtórzeń i konkretnymi argumentami. Bez weryfikacji test sprawdza tylko stan, a nie zachowanie.

Podstawowa składnia to verify(mock).metoda(args) — jest to skrót dla verify(mock, times(1)).metoda(args). Mockito sprawdza, czy w trakcie testu dokładnie raz wywołano daną metodę z takimi argumentami; jeśli nie — rzuca WantedButNotInvoked lub TooFewActualInvocations.

Modyfikator times(N) określa dokładną liczbę wywołań. times(0) jest równoważne never(), ale never() jest bardziej czytelne i wyraża intencję — „ta metoda nie powinna być wywołana". Często używane do weryfikacji ścieżek negatywnych, np. że w przypadku błędu walidacji nie zapisano do bazy.

@Test
void shouldSaveValidUser() {
    UserService service = new UserService(userRepository, emailService);

    service.register(new User("alice@example.com"));

    verify(userRepository).save(any(User.class));        // = times(1)
    verify(emailService, times(1)).sendWelcome("alice@example.com");
    verify(auditLog, never()).logFailure(anyString());   // nigdy
}

@Test
void shouldNotSaveInvalidUser() {
    service.register(new User(""));  // brak emaila

    verify(userRepository, never()).save(any());
    verify(emailService, never()).sendWelcome(anyString());
    verify(auditLog).logFailure("Invalid email");
}

Ważne pułapki:

  • verify musi być wywołane po akcji testowanej (Arrange-Act-Assert) — weryfikuje historię interakcji.
  • Argumenty są porównywane przez equals() — jeśli klasa nie ma poprawnej implementacji, użyj matcherów (any(), eq()) lub ArgumentCaptor.
  • Nie weryfikuj wszystkich możliwych wywołań — to anti-pattern (over-specification). Testuj kontrakt, nie implementację.

Źródło: Mockito javadoc — verification

↑ Powrót na górę

Konfiguracja i resetowanie

31. Czym jest MockSettings i jak konfigurować mocki (withSettings())?

MockSettings to fluent API pozwalający dostosować zachowanie mocka w momencie jego tworzenia. Zamiast podstawowego mock(Foo.class) używamy przeciążonej wersji mock(Foo.class, withSettings()....), łańcuchowo doprecyzowując opcje. Jest to przydatne, gdy domyślne ustawienia Mockito nie wystarczają - np. chcemy nadać mockowi czytelną nazwę w komunikatach błędów, zmienić domyślne odpowiedzi albo włączyć serializację.

Najczęściej używane opcje:

import static org.mockito.Mockito.*;
import static org.mockito.Answers.*;

UserRepository repo = mock(UserRepository.class, withSettings()
    .name("userRepo")                        // czytelna nazwa w błędach
    .defaultAnswer(RETURNS_SMART_NULLS)      // domyślne odpowiedzi
    .verboseLogging()                        // loguje każde wywołanie
    .serializable()                          // mock można serializować
    .extraInterfaces(Closeable.class)        // mock implementuje też Closeable
    .stubOnly()                              // bez śledzenia wywołań (mniej pamięci)
    .lenient()                               // wyłącza strict stubbing
    .withoutAnnotations());                  // ignoruje adnotacje na klasie

Praktyczne zastosowania:

  • .name("xxx") - gdy w teście masz wiele mocków tego samego typu, czytelne nazwy ułatwiają diagnozę: zamiast userRepository1.findById(1) w komunikacie błędu zobaczysz userRepo.findById(1).
  • .defaultAnswer(...) - zmienia globalne zachowanie tego konkretnego mocka, gdy nie chcesz pisać wielu when().thenReturn() (patrz pytanie 32).
  • .extraInterfaces(...) - mock implementuje dodatkowe interfejsy, co bywa potrzebne np. przy testowaniu kodu, który robi if (obj instanceof Closeable).
  • .stubOnly() - optymalizacja pamięci dla mocków używanych masowo (testy parametryzowane); minusem jest brak możliwości verify().
  • .lenient() - lokalnie wyłącza strict stubbing dla danego mocka (zamiast adnotacji @MockitoSettings(strictness = LENIENT) na całej klasie).

Dla większości testów withSettings() nie jest potrzebny - jego użycie powinno być świadomym wyborem motywowanym konkretną potrzebą.

↑ Powrót na górę

Integracja z JUnit i Spring

41. Czym jest @SpyBean i kiedy go używać zamiast @MockBean?

@SpyBean to adnotacja Spring Boot Test, która opakowuje realny bean ze Spring Contextu w szpiega Mockito (Mockito.spy). W przeciwieństwie do @MockBean, który tworzy w pełni sztuczny obiekt z domyślnymi pustymi implementacjami, @SpyBean zachowuje oryginalne zachowanie beanu i pozwala selektywnie nadpisywać wybrane metody. Dzięki temu można testować integrację z rzeczywistą logiką, jednocześnie kontrolując kluczowe punkty.

Cecha @MockBean @SpyBean
Typ obiektu Pełny mock (czysty) Szpieg realnego beanu
Domyślne zachowanie metod Zwraca null/0/false/pustą kolekcję Wywołuje realną implementację
Stubbing metod Wymagany dla wszystkich używanych Tylko dla wybranych metod
Składnia stubbing when(mock.method()).thenReturn(x) doReturn(x).when(spy).method() (zalecane)
Wpływ na bean w kontekście Zastępuje realny bean całkowicie Owija realny bean (delegacja)
Kiedy używać Pełna izolacja zależności Realny bean z punktowymi modyfikacjami
Wydajność Szybsze (brak realnej logiki) Wolniejsze (uruchamia realny kod)
Ryzyko side effects Brak Tak (realne wywołania I/O, DB itp.)

Przykład użycia:

@SpringBootTest
class NotificationServiceTest {

    @Autowired
    private NotificationService notificationService;

    @SpyBean
    private EmailSender emailSender;

    @Test
    void shouldSendEmailButCaptureEvent() {
        doNothing().when(emailSender).send(any(), any());

        notificationService.notifyUser(1L, "Witaj!");

        verify(emailSender, times(1)).send(eq("user@example.com"), eq("Witaj!"));
    }
}

@SpyBean używamy, gdy chcemy zachować realną logikę beanu (np. walidacje, mapowania), ale wyciszyć efekty uboczne (wysyłanie maila, wywołanie zewnętrznego API). @MockBean lepiej sprawdza się, gdy chcemy pełnej izolacji od zależności i nie zależy nam na ich rzeczywistym zachowaniu. Przy @SpyBean zalecana składnia to doReturn().when() zamiast when().thenReturn(), by uniknąć wywołania realnej metody podczas stubbingu.

Źródła: docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/test/mock/mockito/SpyBean.html, site.mockito.org

↑ Powrót na górę

Pułapki i debugowanie

51. Czym jest StrictStubbing i jakie wymusza zasady?

StrictStubbing to tryb pracy Mockito wprowadzony w wersji 2.x i ustawiony jako domyślny w MockitoExtension (JUnit 5) oraz MockitoJUnitRunner.StrictStubs. Jego celem jest wczesne wykrywanie typowych błędów w stubowaniu — zanim doprowadzą do trudnych do zdiagnozowania awarii.

Trzy zasady StrictStubs

  1. Unused stubs — każdy stub zarejestrowany przez when() musi zostać użyty przez testowany kod. W przeciwnym razie Mockito zgłasza UnnecessaryStubbingException (po teście).
  2. Argument mismatch — wywołanie metody mocka z argumentami innymi niż zastubowane rzuca PotentialStubbingProblem (w trakcie testu).
  3. Verification of non-stubbedverify() działa standardowo, ale strict mode podpowiada, gdy weryfikacja redundantnie pokrywa się ze stubbingiem.

Konfiguracja trybu

// JUnit 5 — STRICT_STUBS domyślny
@ExtendWith(MockitoExtension.class)
class StrictTest { }

// JUnit 4
@RunWith(MockitoJUnitRunner.StrictStubs.class)
class LegacyTest { }

// Ręczna konfiguracja
@Rule public MockitoRule rule = MockitoJUnit.rule().strictness(Strictness.STRICT_STUBS);

// Wyłączenie strict dla pojedynczego stuba
lenient().when(mock.foo()).thenReturn("x");

// Lub całej klasy
@MockitoSettings(strictness = Strictness.LENIENT)
class LenientTest { }

Porównanie poziomów strictness

%%{init: {'theme':'base'}}%%
graph TB
    subgraph "Mockito Strictness — porównanie zachowań"
        L["LENIENT<br/>--<br/>Unused stubs: ignorowane<br/>Argument mismatch: ignorowany<br/>Default answer: null/0/false<br/>--<br/>Użycie: legacy, testy z opcjonalnymi stubami"]
        W["STRICT_STUBS (DEFAULT w MockitoExtension)<br/>--<br/>Unused stubs: UnnecessaryStubbingException<br/>Argument mismatch: PotentialStubbingProblem<br/>Default answer: null/0/false + warning<br/>--<br/>Użycie: nowe testy, JUnit 5"]
        S["WARN (deprecated)<br/>--<br/>Unused stubs: ostrzeżenie w konsoli<br/>Argument mismatch: ostrzeżenie<br/>Default answer: null/0/false<br/>--<br/>Użycie: migracja z LENIENT do STRICT"]
    end
    
    L -.-> W
    W -.-> S

Przykład wykrytego błędu

@ExtendWith(MockitoExtension.class)
class OrderTest {
    @Mock PaymentGateway gateway;
    
    @Test
    void shouldCharge() {
        when(gateway.charge(100)).thenReturn(true); // stub dla 100
        new OrderService(gateway).pay(200);          // wywołanie z 200
    }
}

// org.mockito.exceptions.misusing.PotentialStubbingProblem:
// Strict stubbing argument mismatch. Please check:
//  - this invocation: gateway.charge(200);
//  - has following stubbing(s) with different arguments: gateway.charge(100);

Korzyści

  • Szybsze diagnozowanie — błąd pojawia się w miejscu rzeczywistego problemu, nie w odległym NullPointerException.
  • Czystsze testyUnnecessaryStubbingException eliminuje dead code (kopiowane bezmyślnie stuby).
  • Lepsza dokumentacja — każdy stub w teście naprawdę odzwierciedla wymaganą interakcję.

Kiedy używać lenient()

Wyłącznie dla stubów współdzielonych w @BeforeEach, które nie są używane przez wszystkie testy w klasie — np. domyślne zachowanie helpera, które tylko część testów nadpisuje.

Źródło: site.mockito.org/javadoc/current/org/mockito/quality/Strictness.html

↑ Powrót na górę

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 przez MockitoExtension, MockitoAnnotations.openMocks(this) lub MockitoJUnitRunner.
  • Oba sposoby tworzą ten sam typ mocka — różnią się tylko sposobem inicjalizacji.

Źródło: site.mockito.org

↑ Powrót na górę

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.

Źródło: site.mockito.org - ArgumentMatchers

↑ Powrót na górę

Spy - częściowe mocki

26. Czym jest @Spy i jak różni się od @Mock (real method calls vs mock behavior)?

@Spy to częściowy mock w Mockito - wrapuje istniejący, prawdziwy obiekt i pozwala selektywnie zastępować wybrane metody, podczas gdy pozostałe wykonują prawdziwą implementację. Jest to fundamentalna różnica względem @Mock, który tworzy obiekt całkowicie sztuczny, gdzie wszystkie metody zwracają wartości domyślne (null, 0, false, pusta kolekcja).

Kluczowa różnica w zachowaniu:

// @Mock - WSZYSTKO wymyślone (return null/0/empty by default)
@Mock
private List<String> mockList;

@Test
void mockBehavior() {
    mockList.add("element");          // robi NIC (no-op)
    assertEquals(0, mockList.size()); // zwraca 0 (default)
    assertNull(mockList.get(0));      // zwraca null
}

// @Spy - REAL method calls (chyba ze zastubowane)
@Spy
private List<String> spyList = new ArrayList<>();

@Test
void spyBehavior() {
    spyList.add("element");                  // REAL add - element dodany!
    assertEquals(1, spyList.size());         // REAL size = 1
    assertEquals("element", spyList.get(0)); // REAL get
}

Mermaid - flowchart różnicy zachowania:

flowchart TD
    A[Wywołanie metody na obiekcie testowym] --> B{Jaki typ obiektu?}
    B -->|@Mock| C[Mock zawsze przechwytuje]
    B -->|@Spy| D{Metoda zastubowana?}

    C --> E{Metoda zastubowana?}
    E -->|TAK| F[Zwraca zastubowana wartosc]
    E -->|NIE| G[Default value: null/0/false/empty]

    D -->|TAK| H[Zwraca zastubowana wartosc]
    D -->|NIE| I[Wywoluje PRAWDZIWA implementacje]

    G --> J[Brak side effects]
    I --> K[REAL side effects: DB, IO, computation]

    style C fill:#ffcccc
    style D fill:#ccffcc
    style I fill:#fff4cc
    style K fill:#ff9999

Wewnętrzny mechanizm:

  • @Mock - Mockito generuje subklasę (CGLIB/ByteBuddy) gdzie każda metoda jest nadpisana i zwraca Answers.RETURNS_DEFAULTS
  • @Spy - Mockito tworzy proxy nad istniejącą instancją, delegując wywołania do oryginału, chyba że są stubowane

Wymagania @Spy:

// MUSI miec konkretna instancje (nie interfejs bez implementacji)
@Spy
private UserService userService = new UserService(); // ✓ OK

@Spy
private UserService userService; // ✓ OK - Mockito wywoła konstruktor bezargumentowy

@Spy
private UserRepository repo; // ✗ BLAD jesli to interfejs bez implementacji

Werifikacja działa identycznie:

@Spy List<String> spy = new ArrayList<>();
spy.add("test");
verify(spy).add("test"); // dziala tak samo jak na mocku

Spy łączy więc dwa światy: dostaje prawdziwą logikę, ale Mockito tracuje wszystkie interakcje (verify) i pozwala je nadpisywać. Według dokumentacji site.mockito.org, jest to mechanizm przeznaczony głównie do legacy code - w nowym kodzie powinien być sygnałem ostrzegawczym o złym designie.

↑ Powrót na górę

Static i final methods (Mockito 3+)

34. Jak mockować metody statyczne w Mockito 3.4+ (mockStatic)?

Od wersji Mockito 3.4 możliwe jest mockowanie metod statycznych za pomocą API Mockito.mockStatic(Class), które zwraca obiekt MockedStatic<T>. Mechanizm ten opiera się na tzw. inline mock makerze (mockito-inline), który wykorzystuje instrumentację bytecode'u (Byte Buddy + Java Agent) zamiast klasycznych dynamicznych proxy. Pozwala to przechwytywać wywołania metod static, final, a nawet konstruktorów.

Konfiguracja zależności

Dla Mockito 3.x i 4.x trzeba jawnie dodać artefakt mockito-inline:

<!-- Maven -->
<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-inline</artifactId>
    <version>4.11.0</version>
    <scope>test</scope>
</dependency>
// Gradle
testImplementation 'org.mockito:mockito-inline:4.11.0'

Od Mockito 5.x inline mock maker jest domyślny — wystarczy mockito-core (a mockito-inline jest jedynie pustym artefaktem dla kompatybilności wstecznej).

Przykład — mockowanie UUID.randomUUID()

import org.junit.jupiter.api.Test;
import org.mockito.MockedStatic;

import java.util.UUID;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.mockStatic;

class UuidGeneratorTest {

    @Test
    void shouldMockStaticUuid() {
        UUID fixedUuid = UUID.fromString("00000000-0000-0000-0000-000000000001");

        try (MockedStatic<UUID> mocked = mockStatic(UUID.class)) {
            mocked.when(UUID::randomUUID).thenReturn(fixedUuid);

            // Wewnątrz try-with-resources mock jest aktywny
            assertEquals(fixedUuid, UUID.randomUUID());

            // Można też weryfikować wywołania
            mocked.verify(UUID::randomUUID);
        }

        // Poza scope'em try-with-resources mock przestaje działać —
        // UUID.randomUUID() znów zwraca prawdziwy losowy identyfikator.
        assertEquals(false, UUID.randomUUID().equals(fixedUuid));
    }
}

Kluczowe zasady

Zasada Wyjaśnienie
try-with-resources jest obowiązkowy MockedStatic musi zostać zamknięty, inaczej mock "przecieknie" do innych testów w tym samym wątku.
Scope per-thread Mockowanie statyczne działa tylko w wątku, w którym je założono.
Brak globalnego stanu Po zamknięciu zasobu oryginalne metody statyczne wracają.
Nie da się mockować dwóch razy tej samej klasy Powoduje wyjątek MockitoException: static mocking is already registered.

Źródła

↑ Powrót na górę

Wzorce i najlepsze praktyki

43. Jakie są dobre praktyki dotyczące nazewnictwa mocków i organizacji testów?

Dobre nazewnictwo i czytelna struktura testów to fundament utrzymywalnego kodu testowego. Niejasne nazwy testów i chaotyczna organizacja sprawiają, że test po roku staje się zagadką nawet dla autora.

Nazewnictwo mocków

Zmienna trzymająca mock powinna jasno mówić, jaką zależność reprezentuje — używaj nazwy odpowiadającej typowi (camelCase), bez prefiksów typu mock w polach klasy testowej. Prefiks mock wprowadza szum i utrudnia czytanie sekcji when/given i verify/then.

// Źle - prefix "mock" zaciemnia intencję
@Mock private UserRepository mockUserRepository;
@Mock private EmailService mockEmailService;

// Dobrze - nazwa odpowiada typowi zależności
@Mock private UserRepository userRepository;
@Mock private EmailService emailService;

@InjectMocks
private UserRegistrationService userRegistrationService;

Nazewnictwo testów

Nazwa testu to dokumentacja zachowania. Stosuj jedną z dwóch popularnych konwencji w całym projekcie:

Konwencja Przykład Zaleta
metoda_warunek_oczekiwanie save_whenUserExists_shouldThrow Grupowanie alfabetyczne po metodzie
should_X_when_Y should_throw_when_user_exists Czytelne jak zdanie po angielsku
givenX_whenY_thenZ givenExistingUser_whenSave_thenThrow Pełna zgodność z BDD
@Test
void save_whenUserDoesNotExist_shouldStoreUser() { /* ... */ }

@Test
void should_throw_DuplicateUserException_when_email_already_exists() { /* ... */ }

Struktura testu AAA (Arrange-Act-Assert)

Każdy test powinien mieć trzy wyraźne sekcje, najlepiej oddzielone pustą linią lub komentarzem:

@Test
void register_whenEmailIsNew_shouldPersistUser() {
    // Arrange (given)
    User newUser = new User("alice@example.com");
    when(userRepository.existsByEmail("alice@example.com")).thenReturn(false);

    // Act (when)
    userRegistrationService.register(newUser);

    // Assert (then)
    verify(userRepository).save(newUser);
    verify(emailService).sendWelcomeEmail(newUser);
}

Organizacja klas testowych

  • Jedna klasa testowa odpowiada jednej klasie produkcyjnej (UserServiceUserServiceTest).
  • Grupuj testy zagnieżdżonymi klasami @Nested według metody lub scenariusza biznesowego.
  • Wspólny setup przenoś do @BeforeEach, ale unikaj „magicznego” setupu robiącego za dużo — czytelność > DRY.
@ExtendWith(MockitoExtension.class)
class UserRegistrationServiceTest {

    @Mock private UserRepository userRepository;
    @InjectMocks private UserRegistrationService service;

    @Nested
    class WhenEmailIsAlreadyTaken {
        @Test void shouldThrowDuplicateUserException() { /* ... */ }
        @Test void shouldNotSendWelcomeEmail() { /* ... */ }
    }

    @Nested
    class WhenEmailIsNew {
        @Test void shouldPersistUser() { /* ... */ }
    }
}

Źródła: Mockito Best Practices, JUnit 5 User Guide

↑ Powrót na górę

Migracja i kompatybilność

52. Jakie są kluczowe zmiany między Mockito 3.x a 5.x (Java 11+, bytecode manipulation)?

Mockito 5 to przełomowa wersja, która porządkuje wieloletnie kompromisy nagromadzone w gałęzi 3.x. Najważniejsza zmiana to podniesienie minimalnej wersji Javy z 8 do 11, co pozwoliło zespołowi wykorzystać nowoczesne API JVM (m.in. java.lang.invoke, lepsze wsparcie ByteBuddy dla nowszych formatów bytecode) i zrezygnować z wstecznej zgodności blokującej rozwój biblioteki. Drugą fundamentalną zmianą jest uczynienie mockito-inline domyślnym MockMakerem — w Mockito 3.x mockowanie klas final, metod statycznych i konstruktorów wymagało jawnego dodania zależności mockito-inline lub konfiguracji pliku mockito-extensions/org.mockito.plugins.MockMaker. W Mockito 5 ta funkcjonalność działa od razu po dodaniu mockito-core, ponieważ inline MockMaker (oparty o instrumentację JVM przez Java Agent / ByteBuddy attach) jest aktywny domyślnie.

Z punktu widzenia migracji warto przejrzeć kod pod kątem usuniętych deprecated API: Matchers (zastąpione przez ArgumentMatchers już w 2.x), stare warianty verify z Times jako klasą, niektóre metody konfiguracyjne w MockitoAnnotations.initMocks() (preferowane openMocks() zwracające AutoCloseable). Mockito 5 wymaga również nowszej wersji ByteBuddy i nie wspiera już starych JVM-ów (Java 8, 9, 10). Jeśli projekt nadal działa na Javie 8, należy pozostać na linii 4.x (LTS-podobne wsparcie) lub 3.x. Nowy inline MockMaker jest też wolniejszy niż klasyczny subclass MockMaker (różnica rzędu 10–30% w dużych suite'ach), więc dla zespołów, które nigdy nie mockują final, możliwy jest powrót do subclass MockMakera przez konfigurację mock-maker-subclass.

W praktyce migracja 3.x → 5.x dla większości projektów polega na: (1) podniesieniu Javy do 11+, (2) usunięciu zależności mockito-inline z pom.xml/build.gradle (jest już w mockito-core), (3) zastąpieniu initMocks przez openMocks, (4) sprawdzeniu, czy nie używamy klas wewnętrznych usuniętych z publicznego API, (5) przetestowaniu, czy nowa wersja ByteBuddy nie konfliktuje z innymi narzędziami (np. agentami APM, Lombok w starszych wersjach).

%%{init: {'theme':'dark'}}%%
graph TB
    subgraph M3["Mockito 3.x"]
        M3A["Java 8+"]
        M3B["mockito-inline: opt-in"]
        M3C["final/static: dodatkowa zaleznosc"]
        M3D["initMocks deprecated, ale dziala"]
        M3E["Matchers class (deprecated)"]
        M3F["Subclass MockMaker domyslny"]
    end
    subgraph M5["Mockito 5.x"]
        M5A["Java 11+ wymagana"]
        M5B["inline MockMaker DEFAULT"]
        M5C["final/static: out-of-the-box"]
        M5D["openMocks zwraca AutoCloseable"]
        M5E["Matchers usuniete"]
        M5F["Nowsza ByteBuddy + JVM attach"]
    end
    M3A -.migracja.-> M5A
    M3B -.usun zaleznosc.-> M5B
    M3D -.zamien API.-> M5D

Źródła: site.mockito.org/javadoc, github.com/mockito/mockito — release notes 5.0.0.

↑ Powrót na górę

Chcesz więcej pytań?

Uzyskaj dostęp do 800+ pytań z 13 technologii - JavaScript, React, TypeScript, Node.js, SQL i więcej. Wybierz plan 30-dniowy, 90-dniowy lub bezterminowy.

Wybierz plan od 49 zł