MockK è un framework Kotlin-first per creare oggetti mock, progettato specificamente per l'ecosistema Kotlin tenendo conto delle sue caratteristiche linguistiche: coroutine, funzioni di estensione, data class e sealed class. A differenza di Mockito, che è stato portato su Kotlin da Java, MockK è stato progettato fin dall'inizio per la sintassi Kotlin e non richiede plugin aggiuntivi per funzionare con le classi final. Secondo MockK.io, la libreria è utilizzata in più del 40% dei progetti Kotlin con test unitari.
Punti chiave
MockK è una libreria per creare oggetti mock, scritta in Kotlin e ottimizzata per la sua sintassi. Risolve gli stessi problemi di Mockito — isolare il codice testato dalle dipendenze — ma lo fa utilizzando costrutti specifici di Kotlin: lambda, DSL, reified generics e funzioni suspend.
Il principale vantaggio di MockK rispetto alle soluzioni portate è il supporto nativo per Kotlin. In Mockito, simulare una classe final richiede opt-in (mockito-inline) e i metodi statici richiedono mockStatic. MockK lo supporta per impostazione predefinita, poiché le classi Kotlin sono final per impostazione predefinita e aggirare questa limitazione è integrato nell'architettura della libreria.
La versione 1.13.12 (2024) è una versione stabile che supporta Kotlin 2.0, il compilatore K2 e i progetti multipiattaforma (KMP). MockK funziona anche con Kotlin/Native e Kotlin/JS, rendendolo l'unica scelta per i progetti KMP dove né Mockito né EasyMock sono applicabili.
MockK è progettato tenendo conto delle specificità di Kotlin e utilizza le funzionalità del linguaggio — reified generics, DSL con lambda, funzioni inline — per fornire un'API concisa e type-safe senza sacrificare le prestazioni.
Il meccanismo di MockK si basa sull'instrumentazione del bytecode tramite la libreria ByteBuddy (come Mockito), ma la avvolge in un DSL amichevole per Kotlin. Invece di catene when().thenReturn(), MockK utilizza blocchi lambda every { } e coEvery { } che sembrano un'estensione naturale del linguaggio. Sotto il cofano, MockK intercetta la chiamata all'interno del lambda, analizza il metodo e gli argomenti tramite riflessione e li confronta con le regole di stubbing registrate.
Il blocco every { mock.method() } returns value si legge come “ogni volta che il metodo viene chiamato, restituisci il valore.” Questa sintassi dichiarativa è più vicina allo stile Kotlin ed elimina la confusione sull'ordine degli argomenti in when(). Grazie ai reified generics di Kotlin, il tipo del mock viene dedotto automaticamente senza specificare esplicitamente la classe.
val repository = mockk<UserRepository>()
// Stubbing: ogni chiamata a findById(1) restituisce l'utente
every { repository.findById(1) } returns User("Alice")
// Chiamata e verifica
val result = repository.findById(1)
assertEquals("Alice", result.name)
A differenza di Mockito, dove ogni metodo deve essere configurato esplicitamente, MockK supporta il relaxed mock — un mock che restituisce valori predefiniti “ragionevoli” per qualsiasi metodo: una lista vuota per List, 0 per Int, una stringa vuota per String. Questo riduce drasticamente la quantità di codice di configurazione.
// Relaxed mock — tutti i metodi restituiscono valori predefiniti
val api = mockk<ApiService>(relaxed = true)
// Non richiede stubbing — restituisce una lista vuota
println(api.getUsers()) // []
MockK offre diversi modi per creare oggetti mock: mockk<T>() per mock rigorosi (ogni metodo deve essere configurato esplicitamente), mockk<T>(relaxed = true) per mock rilassati e spyk(obj) per creare una spia su un oggetto reale.
| Funzione | Tipo | Comportamento senza stubbing |
|---|---|---|
| mockk() | Mock rigoroso | Lancia un'eccezione quando viene chiamato un metodo non configurato |
| mockk(relaxed = true) | Mock rilassato | Restituisce il valore predefinito |
| spyk() | Spia | Chiama il metodo reale se nessuno stub è configurato |
| slot() | Argument Captor | Cattura l'argomento per la verifica |
La scelta tra mock rigoroso e rilassato dipende dal contesto. Un mock rigoroso garantisce che il test non utilizzi metodi il cui comportamento non è definito — questo aumenta l'affidabilità. Un mock rilassato è conveniente per il prototipazione rapida di test dove non tutte le dipendenze sono importanti. In pratica, si consiglia di iniziare con un mock rigoroso e passare a rilassato solo quando lo stubbing occupa più righe del test stesso.
Il blocco every è il costrutto centrale di stubbing in MockK. All'interno del lambda, viene descritta una chiamata a un metodo con argomenti specifici, quindi un valore viene restituito tramite returns, un'eccezione viene lanciata tramite throws o una risposta viene calcolata tramite answers.
MockK supporta tutti gli scenari necessari per i test: restituire un valore, lanciare un'eccezione, calcolare una risposta basata sugli argomenti e risposte multiple in ordine (sequenza di chiamate).
// Restituire valore
every { repo.findById(1) } returns User("Alice")
// Lanciare eccezione
every { repo.findById(999) } throws NotFoundException()
// Risposta dinamica
every { repo.save(any()) } answers {
val user = firstArg<User>()
user.copy(id = 42)
}
// Sequenza di risposte
every { repo.findAll() } returnsMany listOf(
listOf(User("Alice")),
listOf(User("Bob")),
emptyList()
)
Verify in MockK è simile a Mockito.verify() nello scopo, ma utilizza il DSL Kotlin: verify { mock.method() }. Per le funzioni suspend, si utilizza coVerify { mock.suspendMethod() }, che funziona correttamente con le coroutine e non richiede un runner speciale.
MockK supporta gli stessi modificatori di Mockito: exactly(1), atLeast(2), atMost(5), wasNot(Called). La sintassi è minimalista — il modificatore viene passato come primo argomento in verify { }.
// Verifica: metodo chiamato esattamente 1 volta
verify(exactly = 1) { repo.save(any()) }
// Verificare ordine chiamate
verifySequence {
repo.save(any())
repo.flush()
}
// coVerify per funzioni suspend
coVerify { api.fetchUsers() }
Per la verifica degli argomenti si utilizza slot() — un analogo di ArgumentCaptor. Uno slot viene dichiarato prima della chiamata, passato a every o verify, e dopo l'esecuzione del test contiene il valore catturato.
val userSlot = slot<User>()
verify { repo.save(capture(userSlot)) }
assertEquals("Alice", userSlot.captured.name)
MockK fornisce le annotazioni @MockK e @RelaxedMockK per creare mock tramite inizializzazione in JUnit 5. L'estensione MockKExtension crea automaticamente mock prima di ogni test e pulisce dopo — simile a MockitoExtension, ma con supporto per la modalità rilassata.
L'annotazione @InjectMockKs (o l'alternativa @MockK con creazione esplicita dell'oggetto) inietta i mock nell'istanza testata. Questo riduce il boilerplate e rende il codice del test più pulito.
@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)
}
}
La scelta tra MockK e Mockito dipende dalla composizione del team e dal tipo di progetto. Mockito ha un ecosistema più ampio, più esempi e più integrazioni, ma MockK offre una sintassi Kotlin più pulita e un supporto nativo per le funzionalità del linguaggio. Per i nuovi progetti Kotlin, MockK è raccomandato come soluzione più idiomatica.
| Criterio | MockK | Mockito |
|---|---|---|
| Sintassi | DSL Kotlin (every, verify) | Stile Java (when, thenReturn) |
| Coroutine | coEvery, coVerify (nativo) | Richiede librerie aggiuntive |
| Classe final | Supportato per impostazione predefinita | Richiede mockito-inline |
| KMP | Supportato | Non supportato |
| Relaxed mock | Integrato | Nessun equivalente |
| Popolarità | Crescente nella community Kotlin | Domina nei progetti Java e ibridi |
Per i progetti in Kotlin puro (senza classi Java), MockK è preferibile: meno boilerplate, supporto nativo per le coroutine, nessuna sorpresa con le classi final. Per i progetti ibridi o i team con esperienza Java, Mockito rimane un'opzione funzionante — entrambe le librerie possono essere utilizzate nello stesso progetto attraverso moduli diversi. Durante la migrazione da Mockito a MockK, è sufficiente sostituire le annotazioni @Mock con @MockK e riscrivere i blocchi when().thenReturn() nel formato every { }.
Domande frequenti
Relaxed mock restituisce valori predefiniti per tutti i metodi non configurati (lista vuota, 0, null) senza lanciare eccezioni. Un mock normale (rigoroso) richiede lo stubbing esplicito di ogni metodo — altrimenti il test fallisce. Il relaxed mock è conveniente per test rapidi, il rigoroso per test affidabili.
MockK supporta la simulazione di funzioni di estensione tramite mockkStatic(). Questo è possibile perché le funzioni di estensione in Kotlin sono metodi statici con il ricevitore come primo parametro. Per ogni funzione di estensione, è necessario specificare la classe in cui è dichiarata.
Sì, MockK supporta Kotlin Multiplatform (KMP) per il codice comune. Sulle piattaforme JVM, Native e JS, è possibile utilizzare l'API comune: mockk(), every, verify. Questo rende MockK l'unica scelta per i progetti KMP dove Mockito non funziona.
Utilizza verifySequence { } — un blocco in cui le chiamate sono specificate rigorosamente nell'ordine previsto. Se l'ordine effettivo differisce, verifySequence lancerà un'eccezione indicando la prima chiamata non corrispondente.
Sì, tecnicamente è possibile, ma non raccomandato. Possono verificarsi conflitti a livello di instrumentazione del bytecode (ByteBuddy vs mockito-inline). Se un progetto utilizza già Mockito, la migrazione a MockK può essere graduale attraverso l'isolamento dei moduli.
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