Mock è un oggetto sostitutivo che imita il comportamento di un componente reale e permette di verificare le interazioni con esso. A differenza di Stub, che restituisce semplicemente un valore predefinito, Mock registra il fatto dell'invocazione del metodo, gli argomenti passati e il numero di chiamate. Secondo Mockito (2024), Mock è il tipo più popolare di Test Double nei progetti Java e Kotlin, utilizzato in oltre il 70% dei test unitari delle applicazioni mobili.
Punti chiave
Mock è un oggetto creato da un framework di mocking (Mockito, MockK, EasyMock) che simula un'interfaccia o una classe e registra tutte le chiamate ai suoi metodi. Lo sviluppatore imposta le aspettative: il metodo X verrà chiamato con gli argomenti Y e restituirà Z. Dopo l'esecuzione del test, Mock verifica che le aspettative corrispondano alle chiamate effettive.
Il termine deriva dalla metafora teatrale dei Test Double: un Mock è un “imitatore” che non si limita a stare sul palco (come un Dummy) ma interpreta un ruolo e verifica se l'interazione con esso è stata corretta. Se il codice sotto test non ha chiamato il metodo che Mock si aspettava, o lo ha chiamato con argomenti errati — il test fallisce con un messaggio di aspettativa violata.
Un Mock viene creato tramite la factory del framework: mockk<MyInterface>() o Mockito.mock(MyClass.java). Il framework genera un oggetto proxy che intercetta tutte le chiamate ai metodi. Ogni chiamata viene confrontata con le aspettative predefinite. Se la chiamata corrisponde a un'aspettativa — viene restituito il valore specificato. In caso contrario — Mock restituisce un valore predefinito o lancia un'eccezione, a seconda della configurazione.
Mock è essenziale quando il codice sotto test interagisce con componenti che hanno effetti collaterali: invio di dati al server, scrittura nel database, logging, analisi, navigazione, visualizzazione di dialoghi di sistema. Senza Mock, queste interazioni non possono essere verificate senza eseguire un'infrastruttura reale. Secondo il Google Testing Blog, Mock è l'unico modo per verificare che un'applicazione abbia effettivamente inviato un evento di analisi senza avviare un server di test.
La differenza tra Mock e Stub è uno degli argomenti più dibattuti nei test. Entrambi i tipi sostituiscono una dipendenza reale, ma in modi fondamentalmente diversi.
| Criterio | Mock | Stub |
|---|---|---|
| Domanda principale | Il metodo è stato chiamato? | Quale risultato è stato restituito? |
| Verifica | Comportamento (verify) | Stato (assert) |
| Restituzione dati | Opzionale | Obbligatoria |
| Esempio | verify(analytics).logEvent(“click”) | assertEquals(5, repository.getCount()) |
| Quando usare | Effetti collaterali | Restituzione dati |
Un test semplice per decidere: chiediti “se elimino questa riga di codice, il test fallirà?” Se il test verifica un valore di ritorno — hai bisogno di un Stub (verifica basata su assert). Se il test verifica che il codice abbia chiamato un metodo con gli argomenti corretti — hai bisogno di un Mock (verifica basata su verify). Questa dicotomia segue il pattern Command-Query Separation: i metodi che cambiano stato (commands) necessitano di Mock; i metodi che restituiscono dati (queries) necessitano di Stub.
Scegliere tra Mockito e MockK è una delle prime decisioni quando si imposta lo stack di test per un progetto Android in Kotlin. Entrambe le librerie servono allo stesso scopo, ma con approcci diversi alle funzionalità specifiche di Kotlin.
Mockito è lo standard de facto per i progetti Java. La versione 5.x supporta il mocking per classi final, metodi statici e costruttori grazie al MockMaker integrato. Per i progetti Kotlin, Mockito richiede una configurazione aggiuntiva: estensioni mockito-kotlin per una sintassi migliorata, mockito-inline per le classi final. Mockito non supporta le coroutine Kotlin e le funzioni suspend senza adattatori aggiuntivi.
MockK è stato creato specificamente per Kotlin. Supporta nativamente le coroutine (coEvery, coVerify), le classi sealed, le classi data, i singleton di oggetto e le funzioni di estensione. La sintassi di MockK utilizza DSL con blocchi lambda, che appare naturale nel codice Kotlin. MockK supporta anche il mocking delle proprietà senza configurazione aggiuntiva — questo è importante per i progetti Android che utilizzano LiveData, StateFlow e 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
I benchmark (JVM Benchmark, 2024) mostrano che MockK crea oggetti mock dal 15 al 20% più velocemente di Mockito per i progetti Kotlin grazie al lavoro diretto con il bytecode Kotlin anziché con Java Reflections. Per i progetti con migliaia di test unitari, la differenza nella velocità di compilazione può essere notevole: MockK risparmia 30–60 secondi in un'esecuzione completa dei test nei progetti di grandi dimensioni.
Esaminiamo tre scenari: testare un ViewModel con dipendenze Mock, testare un UseCase con verifica delle chiamate API e testare le coroutine con 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])
}
}
L'uso efficace di Mock nello sviluppo mobile richiede disciplina. Violare queste regole trasforma i test in ostacoli fragili che si rompono a ogni refactoring.
Una regola ferrea: i Mock dovrebbero essere creati solo per le dipendenze che attraversano il confine dell'applicazione: client API, database, file system, servizi di sistema (LocationManager, BluetoothAdapter, Camera). Le classi interne dell'applicazione — entità di dominio, Value Objects, semplici utility — non dovrebbero essere sostituite con Mock. Il loro comportamento viene testato tramite oggetti reali.
Ogni test dovrebbe contenere esattamente un controllo logico — verify (per Mock) o assert (per Stub). Non mescolare la verifica dello stato e del comportamento in un singolo test. Se devi verificare sia una chiamata API che il suo risultato — crea due test separati con nomi diversi. Questa regola, nota come “un assert per test”, risale alle raccomandazioni di Kent Beck (2002).
Oltre al mocking di base, esistono tecniche avanzate che risolvono compiti specifici nello sviluppo mobile: testare il multithreading, verificare lo stato di Flow e il mocking parziale di oggetti reali.
Spy (o mock parziale) permette di creare un oggetto che delega le chiamate all'implementazione reale ma consente di sovrascrivere metodi individuali. In MockK, spyk viene creato basandosi su un'istanza di classe reale: val repo = spyk(InMemoryUserRepository()). Le chiamate con aspettative definite tramite every passano attraverso il Mock; il resto attraverso l'oggetto reale. Spy è particolarmente utile per testare codice legacy dove l'iniezione di dipendenze non è ancora stata implementata e devi sovrascrivere solo un metodo.
Nei progetti Android moderni che utilizzano Jetpack Compose, il ViewModel espone lo stato tramite StateFlow. MockK consente di mockare le dipendenze Flow, e la libreria Turbine semplifica la verifica delle emissioni. Il pattern classico: MockK per un UseCase che restituisce un Flow, Turbine per verificare le emissioni del ViewModel. Questo stack è raccomandato dalla documentazione di Android Testing (Google, 2024) per i progetti che utilizzano 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()
}
}
}
Domande frequenti
Mock è un concetto, un tipo di Test Double che verifica il comportamento. Mockito è una libreria per creare oggetti Mock in Java e Android. Altre librerie: MockK (Kotlin), EasyMock (Java), Cuckoo (iOS).
Per testare le funzioni suspend con Mock, usa MockK (coEvery / coVerify) o Mockito con mockito-kotlin. MockK supporta nativamente le coroutine: coEvery definisce il comportamento di una funzione suspend, coVerify ne verifica la chiamata all'interno di una coroutine. Tutte le chiamate suspend devono essere eseguite all'interno di runTest (kotlinx-coroutines-test).
Sì. In MockK, usa returnsMany: every { api.getData() } returnsMany listOf(response1, response2). In Mockito — una catena di thenReturn(value1).thenReturn(value2). Questo è utile per testare il comportamento con chiamate sequenziali che restituiscono risposte diverse.
In MockK, usa l'annotazione @MockK con relaxed = true e chiama clearMocks(mock) nel metodo @After. In Mockito — Mockito.reset(mock). Buona pratica: creare un nuovo Mock per ogni test tramite @Before per eliminare le interferenze tra i test.
MockK funziona correttamente con le classi sealed: every { useCase() } returns Result.Success(data). Mockito non supporta le classi sealed direttamente, richiedendo soluzioni alternative. Questo è uno dei motivi per cui MockK è raccomandato per i progetti Kotlin anziché Mockito.
Riepilogo
Svilupperemo un'applicazione mobile chiavi in mano
IT Sectr crea applicazioni iOS e Android per startup e aziende dal 2017. Ti consulteremo e ti proporremo la soluzione migliore.
Leggi anche