Mock — cos'è, oggetti mock e librerie per i test

Autore: IT Sectr Pubblicato: 2026-04-10 Tempo di lettura: 8 min

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 che verifica l'interazione: quali metodi sono stati chiamati, con quali argomenti e quante volte
  • Mockito — la libreria più popolare per creare Mock nei progetti Java e Android
  • MockK — un'alternativa a Mockito per Kotlin con supporto nativo per coroutine e classi sealed
  • Behavior verification — la differenza chiave tra Mock e Stub: Mock verifica il comportamento, non lo stato
  • Over-mocking — l'anti-pattern principale: i mock dovrebbero essere usati solo per dipendenze esterne

Cos'è Mock?

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.

Come funziona Mock

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.

Quando Mock è necessario

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.

Mock vs Stub: confronto dettagliato

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.

CriterioMockStub
Domanda principaleIl metodo è stato chiamato?Quale risultato è stato restituito?
VerificaComportamento (verify)Stato (assert)
Restituzione datiOpzionaleObbligatoria
Esempioverify(analytics).logEvent(“click”)assertEquals(5, repository.getCount())
Quando usareEffetti collateraliRestituzione dati

Regola pratica: Mock o no

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.

Mockito vs MockK: confronto librerie

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: classico collaudato

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

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.

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

Confronto delle prestazioni

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.

Esempi di test Mock in Kotlin

Esaminiamo tre scenari: testare un ViewModel con dipendenze Mock, testare un UseCase con verifica delle chiamate API e testare le coroutine con coVerify.

Esempio 1: ViewModel con Mock di analytics

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

Esempio 2: UseCase con verifica asincrona

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

Esempio 3: verifica degli argomenti con 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])
    }
}

Buone pratiche per i test Mock

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.

Mock solo ai confini esterni dell'applicazione

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.

Un assert / verify per test

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

  • Usa relaxUnitFun = true in MockK per i metodi che restituiscono Unit — altrimenti Mock lancia un'eccezione su una chiamata non specificata
  • Limita verify solo alle chiamate critiche — non verificare ogni getter e setter, questo rende i test fragili
  • Applica ArgumentMatchers con giudizio — any() nasconde dettagli importanti se l'argomento è critico per la logica di business
  • Non abusare di verifyNoMoreInteractions — questo metodo rende i test inutilmente rigidi a qualsiasi cambiamento nel codice di produzione
  • Usa @MockkAnnotations per l'inizializzazione automatica degli oggetti Mock — questo riduce il boilerplate e migliora la leggibilità

Tecniche avanzate di test Mock

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.

Mock parziale con spyK

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.

Testare StateFlow con Turbine

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.

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

Domande frequenti

In che modo Mock differisce da Mockito?

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

Come funziona Mock con le coroutine Kotlin?

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

Un Mock può restituire valori diversi a chiamate ripetute?

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.

Come pulire lo stato di Mock tra i test?

In MockK, usa l'annotazione @MockK con relaxed = true e chiama clearMocks(mock) nel metodo @After. In MockitoMockito.reset(mock). Buona pratica: creare un nuovo Mock per ogni test tramite @Before per eliminare le interferenze tra i test.

Come gestisce Mock le classi sealed in Kotlin?

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

  • Mock — un tipo di Test Double che verifica il comportamento (verify), non lo stato (assert) delle dipendenze
  • Mockito — lo standard per Java/Android, MockK — la scelta Kotlin-first con supporto per coroutine e classi sealed
  • Regola principale: Mock per i confini esterni (rete, DB, servizi di sistema), oggetti reali per le classi interne
  • Over-mocking — l'anti-pattern principale: la sostituzione eccessiva delle dipendenze rende i test fragili e poco utili
  • Un test — un controllo logico: verify per Mock o assert per Stub, ma non entrambi nello stesso test
  • ArgumentCaptor / slot — il modo corretto per verificare gli argomenti delle chiamate Mock invece del cieco any()
  • MockK è raccomandato per i progetti Kotlin: coEvery e coVerify funzionano nativamente con le coroutine senza adattatori aggiuntivi

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.

Discuti il progetto

Leggi anche