Mock — este un obiect de substituție care imită comportamentul unui component real și permite verificarea interacțiunii cu acesta. Spre deosebire de Stub, care pur și simplu returnează o valoare dată, Mock înregistrează faptul apelării metodei, argumentele transmise și numărul de apeluri. Conform datelor Mockito (2024), Mock este cel mai popular tip Test Double în proiectele Java și Kotlin, utilizat în peste 70% din testele unitare ale aplicațiilor mobile.
Principalele
Mock — este un obiect creat de framework-ul de mocking (Mockito, MockK, EasyMock), care imită o interfață sau o clasă și înregistrează toate apelurile metodelor sale. Dezvoltatorul stabilește așteptări: metoda X va fi apelată cu argumentele Y și va returna Z. După executarea testului, Mock verifică dacă așteptările coincid cu apelurile reale.
Termenul provine din metafora teatrală a Test Doubles: Mock este un „imitator“ care nu doar stă pe scenă (ca Dummy), ci joacă un rol și verifică dacă interacțiunea cu el a fost corectă. Dacă codul testat nu a apelat metoda pe care Mock o aștepta sau a apelat-o cu argumente incorecte — testul eșuează cu un mesaj despre așteptarea încălcată.
Mock este creat prin fabrica framework-ului: mockk<MyInterface>() sau Mockito.mock(MyClass.java). Framework-ul generează un obiect proxy care interceptează toate apelurile metodelor. Fiecare apel este comparat cu așteptările (expectations) prestabilite. Dacă apelul corespunde așteptării — se returnează valoarea dată. Dacă nu — Mock returnează o valoare implicită sau aruncă o excepție, în funcție de configurare.
Mock este obligatoriu când codul testat interacționează cu componente care au efecte secundare: trimiterea datelor pe server, scrierea în baza de date, logare, analitică, navigare, afișarea dialogurilor de sistem. Fără Mock, aceste interacțiuni nu pot fi verificate fără a porni infrastructura reală. Conform Google Testing Blog, Mock este singura modalitate de a verifica că aplicația a trimis într-adevăr un eveniment de analitică fără a porni un server de test.
Diferența dintre Mock și Stub este unul dintre cele mai discutate subiecte în testare. Ambele tipuri înlocuiesc dependența reală, dar în moduri fundamental diferite.
| Criteriu | Mock | Stub |
|---|---|---|
| Întrebarea principală | A fost apelată metoda? | Ce rezultat s-a returnat? |
| Verificare | Comportamentului (verify) | Stării (assert) |
| Returnarea datelor | Opțional | Obligatoriu |
| Exemplu | verify(analytics).logEvent("click") | assertEquals(5, repository.getCount()) |
| Când se utilizează | Efecte secundare | Returnarea datelor |
Un test simplu pentru alegere: întrebați-vă — „dacă șterg această linie de cod, va cădea testul?“. Dacă testul verifică valoarea returnată — este necesar Stub (verificare prin assert). Dacă testul verifică dacă codul a apelat metoda cu argumentele corecte — este necesar Mock (verificare prin verify). Această dicotomie urmează din modelul Command-Query Separation: metodele care schimbă starea (commands) au nevoie de Mock; metodele care returnează date (queries) au nevoie de Stub.
Alegerea între Mockito și MockK este una dintre primele decizii la configurarea stivei de testare a unui proiect Android în Kotlin. Ambele biblioteci îndeplinesc aceeași sarcină, dar cu o abordare diferită față de specificul Kotlin.
Mockito — este standardul de facto pentru proiectele Java. Versiunea 5.x suportă obiecte mock pentru clase finale, metode statice și constructori datorită MockMaker-ului încorporat. Pentru proiectele Kotlin, Mockito necesită configurare suplimentară: extensii mockito-kotlin pentru sintaxă îmbunătățită, mockito-inline pentru clase finale. Mockito nu suportă corutinele Kotlin și funcțiile suspend fără adaptoare suplimentare.
MockK a fost creat special pentru Kotlin. Suportă nativ corutinele (coEvery, coVerify), sealed class, data class, singleton-uri object și funcții de extensie. Sintaxa MockK utilizează DSL cu blocuri lambda, ceea ce arată natural în codul Kotlin. MockK poate, de asemenea, să mock-uiască proprietăți (property mocking) fără configurare suplimentară — acest lucru este important pentru proiectele Android care folosesc LiveData, StateFlow și Delegates.
// Mockito + mockito-kotlin
val repository = mock<UserRepository>()
whenever(repository.getUser(1)).thenReturn(user)
// MockK
val repository = mockk<UserRepository>()
every { repository.getUser(1) } returns user
Benchmark-urile (JVM Benchmark, 2024) arată că MockK creează obiecte mock cu 15–20% mai rapid decât Mockito pentru proiectele Kotlin datorită lucrului direct cu bytecode-ul Kotlin, nu cu Java Reflections. Pentru proiectele cu mii de teste unitare, diferența în timpul de construire poate fi vizibilă: MockK economisește 30–60 de secunde la rularea completă a testelor în proiecte mari.
Vom examina trei scenarii: testarea ViewModel cu dependențe Mock, testarea UseCase cu verificarea apelului API și testarea corutinelor cu coVerify.
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") }
}
}
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)
}
}
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])
}
}
Utilizarea eficientă a Mock în dezvoltarea mobilă necesită disciplină. Încălcarea acestor reguli transformă testele în obstacole fragile care se strică la fiecare refactorizare.
Regulă strictă: Mock este creat doar pentru dependențele care traversează granița aplicației: clienți API, baze de date, sistemul de fișiere, servicii de sistem (LocationManager, BluetoothAdapter, Camera). Clasele interne ale aplicației — entități de domeniu, Value Object, utilități simple — nu trebuie înlocuite cu Mock. Comportamentul lor se testează prin obiecte reale.
Fiecare test trebuie să conțină exact o verificare logică — fie verify (pentru Mock), fie assert (pentru Stub). Nu amestecați verificarea stării și a comportamentului într-un singur test. Dacă trebuie verificate atât apelul API, cât și rezultatul — creați două teste separate cu nume diferite. Această regulă, cunoscută ca „un assert per test“, provine din recomandările lui Kent Beck (2002).
Pe lângă mocking-ul de bază, există tehnici avansate care rezolvă sarcini specifice în dezvoltarea mobilă: testarea multi-threading-ului, verificarea stării Flow și mocking-ul parțial al obiectelor reale.
Spy (sau partial mock) permite crearea unui obiect care delegă apelurile către implementarea reală, dar permite supradefinirea metodelor individuale. În MockK, spyk este creat pe baza unei instanțe reale a clasei: val repo = spyk(InMemoryUserRepository()). Apelurile pentru care sunt stabilite așteptări prin every trec prin Mock; restul — prin obiectul real. Spy este deosebit de util pentru testarea codului legacy, unde injectarea dependențelor nu a fost încă implementată și trebuie supradefinită o singură metodă.
În proiectele moderne Android pe Jetpack Compose, ViewModel expune starea prin StateFlow. MockK permite mock-uirea dependențelor Flow, iar biblioteca Turbine simplifică verificarea emisiei. Modelul clasic: MockK pentru UseCase care returnează Flow, Turbine pentru verificarea emisiei ViewModel. Acest stack este recomandat de documentația Android Testing (Google, 2024) pentru proiectele pe Kotlin Coroutines.
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()
}
}
}
Întrebări frecvente
Mock — este un concept, un tip Test Double care verifică comportamentul. Mockito — este o bibliotecă pentru crearea obiectelor Mock în Java și Android. Alte biblioteci: MockK (Kotlin), EasyMock (Java), Cuckoo (iOS).
Pentru testarea funcțiilor suspend cu Mock, utilizați MockK (coEvery / coVerify) sau Mockito cu mockito-kotlin. MockK suportă corutinele nativ: coEvery definește comportamentul funcției suspend, coVerify verifică apelul său în interiorul corutinei. Toate apelurile suspend trebuie executate în interiorul runTest (kotlinx-coroutines-test).
Da. În MockK pentru aceasta se folosește returnsMany: every { api.getData() } returnsMany listOf(response1, response2). În Mockito — lanțul thenReturn(value1).thenReturn(value2). Acest lucru este util pentru testarea comportamentului la apeluri succesive cu răspunsuri diferite.
În MockK utilizați adnotarea @MockK cu câmpul relaxed = true și apelați clearMocks(mock) în metoda @After. În Mockito — Mockito.reset(mock). Cea mai bună practică: creați un Mock nou pentru fiecare test prin @Before pentru a exclude influența între teste.
MockK funcționează corect cu sealed class: every { useCase() } returns Result.Success(data). Mockito nu suportă sealed class direct, necesitând soluții ocolitoare. Acesta este unul dintre motivele pentru care pentru proiectele Kotlin se recomandă MockK în loc de Mockito.
Rezumat
Vom dezvolta o aplicație mobilă la cheie
IT Sectr creează aplicații iOS și Android pentru startup-uri și afaceri din 2017. Vă vom consilia și vă vom propune cea mai bună soluție.
Citiți și