Mock — co to jest, obiekty mock i biblioteki do testów

Autor: IT Sectr Opublikowano: 2026-04-10 Czas czytania: 8 min

Mock — to obiekt zastępczy, który imituje zachowanie rzeczywistego komponentu i umożliwia weryfikację interakcji z nim. W przeciwieństwie do Stub, który po prostu zwraca zadaną wartość, Mock rejestruje fakt wywołania metody, przekazane argumenty i liczbę wywołań. Według danych Mockito (2024), Mock to najpopularniejszy typ Test Double w projektach Java i Kotlin, używany w ponad 70% testów jednostkowych aplikacji mobilnych.

Najważniejsze

  • Mock — obiekt weryfikujący interakcje: które metody zostały wywołane, z jakimi argumentami i ile razy
  • Mockito — najpopularniejsza biblioteka do tworzenia Mock w Java i Android
  • MockK — alternatywa dla Mockito dla Kotlin z natywnym wsparciem korutyn i sealed class
  • Behavior verification — kluczowa różnica między Mock a Stub: Mock weryfikuje zachowanie, a nie stan
  • Over-mocking — główny Anti-Pattern: mock powinien być tylko dla zewnętrznych zależności

Co to jest Mock?

Mock — to obiekt tworzony przez framework mockingowy (Mockito, MockK, EasyMock), który imituje interfejs lub klasę i rejestruje wszystkie wywołania swoich metod. Programista określa oczekiwania: metoda X zostanie wywołana z argumentami Y i zwróci Z. Po wykonaniu testu Mock sprawdza, czy oczekiwania są zgodne z rzeczywistymi wywołaniami.

Termin pochodzi z teatralnej metafory Test Doubles: Mock to „imitator“, który nie tylko stoi na scenie (jak Dummy), ale odgrywa rolę i weryfikuje, czy interakcja z nim była prawidłowa. Jeśli testowany kod nie wywołał metody, której Mock oczekiwał, lub wywołał ją z nieprawidłowymi argumentami — test kończy się niepowodzeniem z komunikatem o naruszonym oczekiwaniu.

Jak działa Mock

Mock jest tworzony przez fabrykę frameworka: mockk<MyInterface>() lub Mockito.mock(MyClass.java). Framework generuje obiekt proxy, który przechwytuje wszystkie wywołania metod. Każde wywołanie jest porównywane z wcześniej zdefiniowanymi oczekiwaniami (expectations). Jeśli wywołanie odpowiada oczekiwaniu — zwracana jest zadana wartość. Jeśli nie — Mock zwraca wartość domyślną lub zgłasza wyjątek, w zależności od konfiguracji.

Kiedy Mock jest niezbędny

Mock jest obowiązkowy, gdy testowany kod współdziała z komponentami mającymi efekty uboczne: wysyłanie danych na serwer, zapis do bazy danych, logowanie, analityka, nawigacja, wyświetlanie okien systemowych. Bez Mocka te interakcje są niemożliwe do zweryfikowania bez uruchamiania rzeczywistej infrastruktury. Według Google Testing Blog, Mock to jedyny sposób, aby sprawdzić, czy aplikacja rzeczywiście wysłała zdarzenie analityczne bez uruchamiania serwera testowego.

Mock i Stub: szczegółowe porównanie

Różnica między Mock a Stub to jeden z najczęściej dyskutowanych tematów w testowaniu. Oba typy zastępują rzeczywistą zależność, ale w zasadniczo różny sposób.

KryteriumMockStub
Główne pytanieCzy metoda została wywołana?Jaki wynik został zwrócony?
WeryfikacjaZachowania (verify)Stanu (assert)
Zwrot danychOpcjonalnieObowiązkowo
Przykładverify(analytics).logEvent("click")assertEquals(5, repository.getCount())
Kiedy stosowaćEfekty uboczneZwrot danych

Praktyczna zasada: Mock czy nie

Prosty test wyboru: zadaj sobie pytanie — „jeśli usunę tę linię kodu, czy test upadnie?“. Jeśli test weryfikuje wartość zwracaną — potrzebny jest Stub (weryfikacja przez assert). Jeśli test sprawdza, czy kod wywołał metodę z prawidłowymi argumentami — potrzebny jest Mock (weryfikacja przez verify). Ta dychotomia wynika ze wzorca Command-Query Separation: metody zmieniające stan (commands) wymagają Mocka; metody zwracające dane (queries) wymagają Stuba.

Mockito i MockK: porównanie bibliotek

Wybór między Mockito a MockK to jedna z pierwszych decyzji przy konfiguracji stosu testowego projektu Android w Kotlin. Obie biblioteki wykonują to samo zadanie, ale różnią się podejściem do specyfiki Kotlin.

Mockito: sprawdzona klasyka

Mockito — de facto standard dla projektów Java. Wersja 5.x obsługuje obiekty mock dla klas finalnych, metod statycznych i konstruktorów dzięki wbudowanemu MockMaker. Dla projektów Kotlin Mockito wymaga dodatkowej konfiguracji: rozszerzenia mockito-kotlin dla lepszej składni, mockito-inline dla klas finalnych. Mockito nie obsługuje korutyn Kotlin i funkcji suspend bez dodatkowych adapterów.

MockK: podejście Kotlin-first

MockK został stworzony specjalnie dla Kotlin. Natywnie obsługuje korutyny (coEvery, coVerify), sealed class, data class, singleton-y object i funkcje rozszerzające. Składnia MockK wykorzystuje DSL z blokami lambda, co wygląda naturalnie w kodzie Kotlin. MockK potrafi także mockować właściwości (property mocking) bez dodatkowej konfiguracji — to ważne dla projektów Android używających LiveData, StateFlow i Delegates.

kotlin
// Mockito + mockito-kotlin
val repository = mock<UserRepository>()
whenever(repository.getUser(1)).thenReturn(user)

// MockK
val repository = mockk<UserRepository>()
every { repository.getUser(1) } returns user

Porównanie wydajności

Benchmarki (JVM Benchmark, 2024) pokazują, że MockK tworzy obiekty mock o 15–20% szybciej niż Mockito dla projektów Kotlin dzięki bezpośredniej pracy z bytecodem Kotlin, a nie Java Reflections. Dla projektów z tysiącami testów jednostkowych różnica w czasie budowania może być zauważalna: MockK oszczędza 30–60 sekund przy pełnym przebiegu testów w dużych projektach.

Przykłady testów Mock w Kotlin

Rozpatrzymy trzy scenariusze: testowanie ViewModel z zależnościami Mock, testowanie UseCase z weryfikacją wywołania API i testowanie korutyn z coVerify.

Przykład 1: ViewModel z mockowaną analityką

kotlin
class ProfileViewModelTest {
    private val analytics = mockk<AnalyticsService>()
    private val repo = mockk<UserRepository>()
    private val vm = ProfileViewModel(repo, analytics)

    fun `profile opened logs analytics event`() {
        every { analytics.logEvent("profile_opened") } returns Unit

        vm.onViewCreated()

        verify { analytics.logEvent("profile_opened") }
    }
}

Przykład 2: UseCase z weryfikacją asynchroniczną

kotlin
class SendMessageUseCaseTest {
    private val api = mockk<MessagingApi>()
    private val useCase = SendMessageUseCase(api)

    fun `send message with correct payload`() = runTest {
        val message = Message(text = "Hello", userId = 42)

        coEvery { api.sendMessage(any()) } returns MessageResult.Sent("msg_1")

        val result = useCase.execute(message)

        coVerify {
            api.sendMessage(match {
                it.text == "Hello" && it.userId == 42
            })
        }
        assertTrue(result is MessageResult.Sent)
    }
}

Przykład 3: weryfikacja argumentów z ArgumentCaptor

kotlin
class OrderUseCaseTest {
    private val api = mockk<OrderApi>()
    private val useCase = OrderUseCase(api)
    private val slot = slot<OrderRequest>()

    fun `order request contains correct items`() = runTest {
        coEvery { api.placeOrder(capture(slot)) } returns OrderResult.Placed("order_1")

        useCase.execute(listOf("item_a", "item_b"))

        assertEquals(2, slot.captured.items.size)
        assertEquals("item_a", slot.captured.items[0])
    }
}

Najlepsze praktyki testowania z Mock

Efektywne wykorzystanie Mock w programowaniu mobilnym wymaga przestrzegania dyscypliny. Naruszenie tych zasad zamienia testy w kruche przeszkody, które psują się przy każdym refaktoringu.

Mockuj tylko zewnętrzne granice aplikacji

Sztywna zasada: Mock jest tworzony tylko dla zależności przekraczających granicę aplikacji: klienty API, bazy danych, system plików, usługi systemowe (LocationManager, BluetoothAdapter, Camera). Wewnętrzne klasy aplikacji — encje domenowe, Value Object, proste narzędzia — nie powinny być zastępowane przez Mock. Ich zachowanie testuje się przez rzeczywiste obiekty.

Jeden assert/verify na test

Każdy test powinien zawierać dokładnie jedną logiczną kontrolę — albo verify (dla Mocka), albo assert (dla Stuba). Nie mieszaj weryfikacji stanu i zachowania w jednym teście. Jeśli trzeba sprawdzić zarówno wywołanie API, jak i wynik — utwórz dwa osobne testy z różnymi nazwami. Ta zasada, znana jako „jeden assert na test“, wywodzi się z zaleceń Kenta Becka (2002).

  • Używaj relaxUnitFun = true w MockK dla metod zwracających Unit — w przeciwnym razie Mock rzuci wyjątek na nieopisane wywołanie
  • Ograniczaj verify tylko do krytycznych wywołań — nie weryfikuj każdego gettera i settera, to czyni testy kruchymi
  • Stosuj ArgumentMatchers rozsądnie — any() ukrywa ważne szczegóły, jeśli argument jest krytyczny dla logiki biznesowej
  • Nie nadużywaj verifyNoMoreInteractions — ta metoda czyni test niepotrzebnie sztywnym na wszelkie zmiany w kodzie produkcyjnym
  • Używaj @MockkAnnotations do automatycznej inicjalizacji obiektów Mock — to redukuje boilerplate i poprawia czytelność

Zaawansowane techniki testowania z Mock

Oprócz podstawowego mockowania istnieją zaawansowane techniki rozwiązujące specyficzne problemy w programowaniu mobilnym: testowanie wielowątkowości, weryfikacja stanu Flow i częściowe mockowanie rzeczywistych obiektów.

Partial Mock z spyK

Spy (lub partial mock) umożliwia utworzenie obiektu, który deleguje wywołania do rzeczywistej implementacji, ale pozwala nadpisać poszczególne metody. W MockK spyk jest tworzony na podstawie rzeczywistej instancji klasy: val repo = spyk(InMemoryUserRepository()). Wywołania, dla których zdefiniowano oczekiwania przez every, przechodzą przez Mock; pozostałe — przez rzeczywisty obiekt. Spy jest szczególnie przydatny do testowania legacy-kodu, gdzie wstrzykiwanie zależności nie zostało jeszcze wprowadzone, a trzeba nadpisać tylko jedną metodę.

Testowanie StateFlow z Turbine

W nowoczesnych projektach Android na Jetpack Compose ViewModel eksponuje stan przez StateFlow. MockK umożliwia mockowanie zależności Flow, a biblioteka Turbine upraszcza weryfikację emisji. Klasyczny wzorzec: MockK dla UseCase zwracającego Flow, Turbine do weryfikacji emisji ViewModel. Ten stos jest zalecany przez dokumentację Android Testing (Google, 2024) dla projektów na Kotlin Coroutines.

kotlin
class SearchViewModelTest {
    private val searchUseCase = mockk<SearchUseCase>()
    private val vm = SearchViewModel(searchUseCase)

    fun `search emits results`() = runTest {
        coEvery { searchUseCase.search("android") } returns
            flowOf(SearchResult.Success(listOf(Item("Android TDD"))))

        vm.search("android")

        vm.state.test {
            val state = awaitItem()
            assertTrue(state.items.isNotEmpty())
            cancelAndIgnoreRemainingEvents()
        }
    }
}

Często zadawane pytania

Czym różni się Mock od Mockito?

Mock — to koncepcja, typ Test Double weryfikujący zachowanie. Mockito — to biblioteka do tworzenia obiektów Mock w Java i Android. Inne biblioteki: MockK (Kotlin), EasyMock (Java), Cuckoo (iOS).

Jak Mock współpracuje z korutynami Kotlin?

Do testowania funkcji suspend z Mock używaj MockK (coEvery / coVerify) lub Mockito z mockito-kotlin. MockK obsługuje korutyny natywnie: coEvery określa zachowanie funkcji suspend, coVerify weryfikuje jej wywołanie wewnątrz korutyny. Wszystkie wywołania suspend muszą być wykonywane wewnątrz runTest (kotlinx-coroutines-test).

Czy Mock może zwracać różne wartości przy kolejnych wywołaniach?

Tak. W MockK służy do tego returnsMany: every { api.getData() } returnsMany listOf(response1, response2). W Mockito — łańcuch thenReturn(value1).thenReturn(value2). Jest to przydatne do testowania zachowania przy sekwencyjnych wywołaniach z różnymi odpowiedziami.

Jak wyczyścić stan Mock między testami?

W MockK używaj adnotacji @MockK z polem relaxed = true i wywołuj clearMocks(mock) w metodzie @After. W MockitoMockito.reset(mock). Najlepszą praktyką jest tworzenie nowego Mocka dla każdego testu przez @Before, aby wykluczyć wpływ między testami.

Jak Mock obsługuje sealed class w Kotlin?

MockK poprawnie współpracuje z sealed class: every { useCase() } returns Result.Success(data). Mockito nie obsługuje sealed class bezpośrednio, wymagając obejść. To jeden z powodów, dla których w projektach Kotlin zaleca się MockK zamiast Mockito.

Podsumowanie

  • Mock — typ Test Double weryfikujący zachowanie (verify), a nie stan (assert) zależności
  • Mockito — standard dla Java/Android, MockK — wybór Kotlin-first z wsparciem korutyn i sealed class
  • Główna zasada: Mock dla zewnętrznych granic (sieć, BD, usługi systemowe), rzeczywiste obiekty dla wewnętrznych klas
  • Over-mocking — główny Anti-Pattern: nadmierne zastępowanie zależności czyni testy kruchymi i mało przydatnymi
  • Jeden test — jedna logiczna kontrola: verify dla Mocka lub assert dla Stuba, ale nie oba w jednym teście
  • ArgumentCaptor / slot — prawidłowy sposób weryfikacji argumentów wywołania Mock zamiast ślepego any()
  • MockK jest zalecany dla projektów Kotlin: coEvery i coVerify natywnie współpracują z korutynami bez dodatkowych adapterów

Opracujemy aplikację mobilną pod klucz

IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.

Omów projekt

Przeczytaj również