Mock — ce este, obiectele mock și bibliotecile pentru teste

Autor: IT Sectr Publicat: 2026-04-10 Timp de citire: 8 min

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 — obiect care verifică interacțiunile: ce metode au fost apelate, cu ce argumente și de câte ori
  • Mockito — cea mai populară bibliotecă pentru crearea Mock în Java și Android
  • MockK — alternativă la Mockito pentru Kotlin cu suport nativ pentru corutine și sealed class
  • Behavior verification — diferența cheie între Mock și Stub: Mock verifică comportamentul, nu starea
  • Over-mocking — principalul Anti-Pattern: mock trebuie să fie doar pentru dependențe externe

Ce este Mock?

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ă.

Cum funcționează Mock

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.

Când este necesar Mock

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.

Mock și Stub: comparație detaliată

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.

CriteriuMockStub
Întrebarea principalăA fost apelată metoda?Ce rezultat s-a returnat?
VerificareComportamentului (verify)Stării (assert)
Returnarea datelorOpționalObligatoriu
Exempluverify(analytics).logEvent("click")assertEquals(5, repository.getCount())
Când se utilizeazăEfecte secundareReturnarea datelor

Regula practică: Mock sau nu

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.

Mockito și MockK: comparația bibliotecilor

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: clasica dovedită

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: abordarea Kotlin-first

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.

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

Compararea performanței

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.

Exemple de teste Mock în Kotlin

Vom examina trei scenarii: testarea ViewModel cu dependențe Mock, testarea UseCase cu verificarea apelului API și testarea corutinelor cu coVerify.

Exemplul 1: ViewModel cu analitică mock-uită

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") }
    }
}

Exemplul 2: UseCase cu verificare asincronă

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)
    }
}

Exemplul 3: verificarea argumentelor cu 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])
    }
}

Cele mai bune practici de testare cu Mock

Utilizarea eficientă a Mock în dezvoltarea mobilă necesită disciplină. Încălcarea acestor reguli transformă testele în obstacole fragile care se strică la fiecare refactorizare.

Mock-uiți doar granițele externe ale aplicației

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.

Un assert/verify per test

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).

  • Utilizați relaxUnitFun = true în MockK pentru metodele care returnează Unit — altfel Mock va arunca o excepție la un apel nedescis
  • Limitați verify doar la apelurile critice — nu verificați fiecare getter și setter, acest lucru face testele fragile
  • Aplicați ArgumentMatchers cu sens — any() ascunde detalii importante dacă argumentul este critic pentru logica de business
  • Nu abuzați de verifyNoMoreInteractions — această metodă face testul inutil de rigid la orice schimbare în codul de producție
  • Utilizați @MockkAnnotations pentru inițializarea automată a obiectelor Mock — reduce boilerplate-ul și îmbunătățește lizibilitatea

Tehnici avansate de testare cu Mock

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.

Partial Mock cu spyK

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ă.

Testarea StateFlow cu Turbine

Î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.

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()
        }
    }
}

Întrebări frecvente

Cu ce diferă Mock de Mockito?

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).

Cum funcționează Mock cu corutinele Kotlin?

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).

Poate Mock să returneze valori diferite la apeluri repetate?

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.

Cum se curăță starea Mock între teste?

În MockK utilizați adnotarea @MockK cu câmpul relaxed = true și apelați clearMocks(mock) în metoda @After. În MockitoMockito.reset(mock). Cea mai bună practică: creați un Mock nou pentru fiecare test prin @Before pentru a exclude influența între teste.

Cum gestionează Mock sealed class în Kotlin?

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

  • Mock — tip Test Double care verifică comportamentul (verify), nu starea (assert) dependențelor
  • Mockito — standard pentru Java/Android, MockK — alegere Kotlin-first cu suport pentru corutine și sealed class
  • Regula principală: Mock pentru granițele externe (rețea, BD, servicii de sistem), obiecte reale pentru clasele interne
  • Over-mocking — Anti-Pattern principal: înlocuirea excesivă a dependențelor face testele fragile și puțin utile
  • Un test — o verificare logică: verify pentru Mock sau assert pentru Stub, dar nu ambele într-un singur test
  • ArgumentCaptor / slot — modalitatea corectă de verificare a argumentelor apelului Mock în loc de any() orb
  • MockK este recomandat pentru proiecte Kotlin: coEvery și coVerify funcționează nativ cu corutinele fără adaptoare suplimentare

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.

Discutați proiectul

Citiți și