MockK — to framework Kotlin-first do tworzenia obiektów mock, zaprojektowany specjalnie dla ekosystemu Kotlin z uwzględnieniem jego cech językowych: korutyn, funkcji rozszerzających, data class i sealed class. W przeciwieństwie do Mockito, który został przeniesiony na Kotlin z Javy, MockK od początku był projektowany pod składnię Kotlina i nie wymaga dodatkowych wtyczek do pracy z klasami final. Według danych MockK.io, biblioteka jest używana w ponad 40% projektów Kotlin z testami jednostkowymi.
Najważniejsze
MockK — to biblioteka do tworzenia obiektów mock, napisana w Kotlin i zoptymalizowana pod jego składnię. Rozwiązuje te same zadania co Mockito — izolację testowanego kodu od zależności — ale robi to z wykorzystaniem konstrukcji specyficznych dla Kotlina: lambd, DSL, reified generics i suspend-funkcji.
Główna zaleta MockK nad przeniesionymi rozwiązaniami — natywne wsparcie Kotlina. W Mockito mockowanie final-class wymaga opt-in (mockito-inline), a metod statycznych — mockStatic. MockK obsługuje to domyślnie, ponieważ klasy Kotlina domyślnie są finalne, a obejście tego ograniczenia jest wbudowane w architekturę biblioteki.
Wersja 1.13.12 (2024) — stabilne wydanie, obsługujące Kotlin 2.0, kompilator K2 i projekty wieloplatformowe (KMP). MockK działa również z Kotlin/Native i Kotlin/JS, co czyni go jedynym wyborem dla projektów KMP, gdzie ani Mockito, ani EasyMock nie mają zastosowania.
MockK został zaprojektowany z uwzględnieniem specyfiki Kotlina i wykorzystuje możliwości języka — reified generics, DSL z lambdami, funkcje inline — aby zapewnić zwięzłe i bezpieczne typowo API bez utraty wydajności.
Mechanizm MockK opiera się na instrumentacji bajtkodu za pomocą biblioteki ByteBuddy (podobnie jak Mockito), ale opakowuje ją w przyjazny dla Kotlina DSL. Zamiast łańcuchów when().thenReturn() MockK używa lambdowych bloków every { } i coEvery { }, które wyglądają jak naturalne rozszerzenie języka. Pod maską MockK przechwytuje wywołanie wewnątrz lambdy, analizuje metodę i argumenty przez refleksję i dopasowuje do zapisanych reguł stubbingu.
Blok every { mock.method() } returns value czyta się jako „każdym razem, gdy metoda jest wywoływana, zwróć wartość”. Taki deklaratywny składnia jest bliższa stylowi Kotlina i eliminuje pomyłki z kolejnością argumentów w when(). Dzięki reified generics Kotlina, typ mocka jest wyprowadzany automatycznie bez jawnego określania klasy.
val repository = mockk<UserRepository>()
// Stubbing: każde wywołanie findById(1) zwraca użytkownika
every { repository.findById(1) } returns User("Alice")
// Wywołanie i weryfikacja
val result = repository.findById(1)
assertEquals("Alice", result.name)
W przeciwieństwie do Mockito, gdzie każdą metodę trzeba konfigurować jawnie, MockK obsługuje relaxed mock — mock, który zwraca „rozsądne” wartości domyślne dla dowolnej metody: pustą listę dla List, 0 dla Int, pusty ciąg znaków dla String. To radykalnie skraca ilość kodu przygotowawczego.
// Relaxed mock — wszystkie metody zwracają wartości domyślne
val api = mockk<ApiService>(relaxed = true)
// Nie wymaga stubbingu — zwróci pustą listę
println(api.getUsers()) // []
MockK oferuje kilka sposobów tworzenia obiektów mock: mockk<T>() dla ścisłego mocka (każda metoda musi być jawnie skonfigurowana), mockk<T>(relaxed = true) dla zrelaksowanego mocka oraz spyk(obj) do tworzenia szpiega na rzeczywistym obiekcie.
| Funkcja | Typ | Zachowanie bez stubbingu |
|---|---|---|
| mockk() | Ścisły mock | Wyrzuca wyjątek przy wywołaniu nie-ustawionej metody |
| mockk(relaxed = true) | Zrelaksowany mock | Zwraca wartość domyślną |
| spyk() | Szpieg | Wywołuje rzeczywistą metodę, jeśli nie skonfigurowano stuba |
| slot() | Argument Captor | Przechwytuje argument do weryfikacji |
Wybór między ścisłym a zrelaksowanym mockiem zależy od kontekstu. Ścisły mock gwarantuje, że test nie używa metod, których zachowanie nie jest zdefiniowane — zwiększa to niezawodność. Zrelaksowany mock jest wygodny do szybkiego prototypowania testów, gdzie nie wszystkie zależności są istotne. W praktyce zaleca się zaczynać od ścisłego mocka i przełączać się na relaxed tylko wtedy, gdy stubbing zajmuje więcej linii niż sam test.
Blok every — to centralna konstrukcja stubbingu w MockK. Wewnątrz lambdy opisuje się wywołanie metody z konkretnymi argumentami, a następnie zwraca się wartość przez returns, wyrzuca wyjątek przez throws lub oblicza odpowiedź przez answers.
MockK obsługuje wszystkie scenariusze potrzebne do testowania: zwracanie wartości, wyrzucanie wyjątku, obliczanie odpowiedzi na podstawie argumentów, wiele odpowiedzi po kolei (sekwencja wywołań).
// Zwracanie wartości
every { repo.findById(1) } returns User("Alice")
// Rzucanie wyjątku
every { repo.findById(999) } throws NotFoundException()
// Dynamiczna odpowiedź
every { repo.save(any()) } answers {
val user = firstArg<User>()
user.copy(id = 42)
}
// Sekwencja odpowiedzi
every { repo.findAll() } returnsMany listOf(
listOf(User("Alice")),
listOf(User("Bob")),
emptyList()
)
Verify w MockK jest analogiczny do Mockito.verify() w znaczeniu, ale używa DSL Kotlina: verify { mock.method() }. Dla suspend-funkcji używa się coVerify { mock.suspendMethod() }, który poprawnie działa z korutynami i nie wymaga specjalnego runnera.
MockK obsługuje te same modyfikatory co Mockito: exactly(1), atLeast(2), atMost(5), wasNot(Called). Składnia jest minimalistyczna — modyfikator przekazywany jest jako pierwszy argument w verify { }.
// Weryfikacja: metoda wywołana dokładnie 1 raz
verify(exactly = 1) { repo.save(any()) }
// Weryfikacja kolejności wywołań
verifySequence {
repo.save(any())
repo.flush()
}
// coVerify dla funkcji suspend
coVerify { api.fetchUsers() }
Do weryfikacji argumentów używa się slot() — analogu ArgumentCaptor. Slot jest deklarowany przed wywołaniem, przekazywany do every lub verify, a po wykonaniu testu zawiera przechwyconą wartość.
val userSlot = slot<User>()
verify { repo.save(capture(userSlot)) }
assertEquals("Alice", userSlot.captured.name)
MockK udostępnia adnotacje @MockK i @RelaxedMockK do tworzenia mocków przez inicjalizację w JUnit 5. Rozszerzenie MockKExtension automatycznie tworzy mocki przed każdym testem i czyści po — analogicznie do MockitoExtension, ale z obsługą trybu relaxed.
Adnotacja @InjectMockKs (lub alternatywa @MockK z jawnym tworzeniem obiektu) wstrzykuje mocki do testowanego egzemplarza. To skraca boilerplate i czyni kod testu czystszym.
@ExtendWith(MockKExtension::class)
class UserServiceTest {
@MockK
lateinit var repository: UserRepository
@InjectMockKs
lateinit var service: UserService
@Test
fun `getUser returns user from repository`() {
every { repository.findById(1) } returns User("Alice")
assertEquals("Alice", service.getUser(1)?.name)
}
}
Wybór między MockK a Mockito zależy od składu zespołu i typu projektu. Mockito ma większą ekosystem, więcej przykładów i integracji, ale MockK zapewnia czystszą składnię Kotlina i natywne wsparcie dla funkcji językowych. Dla nowych projektów Kotlin MockK jest zalecany jako bardziej idiomatyczne rozwiązanie.
| Kryterium | MockK | Mockito |
|---|---|---|
| Składnia | Kotlin DSL (every, verify) | Styl Java (when, thenReturn) |
| Korutyny | coEvery, coVerify (natywnie) | Wymaga dodatkowych bibliotek |
| Final class | Obsługiwane domyślnie | Wymaga mockito-inline |
| KMP | Obsługiwane | Nieobsługiwane |
| Relaxed mock | Wbudowany | Brak odpowiednika |
| Popularność | Rosnąca w społeczności Kotlin | Dominuje w Javie i projektach hybrydowych |
Dla projektów w czystym Kotlinie (bez klas Java) MockK jest preferowany: mniej boilerplate, natywne wsparcie korutyn, brak niespodzianek z klasami final. Dla projektów hybrydowych lub zespołów z doświadczeniem w Javie Mockito pozostaje działającym rozwiązaniem — obie biblioteki mogą być używane w jednym projekcie przez różne moduły. Przy migracji z Mockito na MockK wystarczy zastąpić adnotacje @Mock na @MockK i przepisać bloki when().thenReturn() w format every { }.
Często zadawane pytania
Relaxed mock zwraca wartości domyślne dla wszystkich nie-ustawionych metod (pusta lista, 0, null), nie wyrzucając wyjątków. Zwykły (strict) mock wymaga jawnego stubbingu każdej metody — w przeciwnym razie test pada. Relaxed mock jest wygodny do szybkich testów, strict — do niezawodnych.
MockK obsługuje mockowanie funkcji rozszerzających przez mockkStatic(). Jest to możliwe, ponieważ funkcje rozszerzające w Kotlinie są metodami statycznymi z pierwszym parametrem-odbiorcą. Dla każdej funkcji rozszerzającej należy określić klasę, w której jest zadeklarowana.
Tak, MockK obsługuje Kotlin Multiplatform (KMP) dla kodu wspólnego. Na platformach JVM, Native i JS można używać wspólnego API mockk(), every, verify. To czyni MockK jedynym wyborem dla projektów KMP, gdzie Mockito nie działa.
Użyj verifySequence { } — bloku, w którym wywołania są określone ściśle w oczekiwanej kolejności. Jeśli rzeczywista kolejność się różni, verifySequence wyrzuci wyjątek z informacją o pierwszym niezgodnym wywołaniu.
Tak, technicznie jest to możliwe, ale nie zalecane. Konflikty mogą wystąpić na poziomie instrumentacji bajtkodu (ByteBuddy vs mockito-inline). Jeśli projekt już używa Mockito, migracja na MockK może być stopniowa poprzez izolację modułów.
Podsumowanie
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.
Przeczytaj również