Stub (stub, double di test) — un oggetto di test che restituisce risposte predefinite alle chiamate di metodo anziché un’implementazione reale. Nello sviluppo mobile, gli stub isolano il modulo testato dalle richieste di rete, database e filesystem, consentendo di verificare la logica senza configurazione dell’ambiente. A differenza di mock, stub non verifica il comportamento — fornisce solo dati. Maggiori dettagli nell’articolo di Martin Fowler sui test doubles.
Punti chiave
Stub è un oggetto double di test che sostituisce una dipendenza reale in un test e restituisce valori predefiniti per chiamate specifiche. Il termine è stato introdotto nella classificazione di Gerard Meszaros (2007) nel libro «xUnit Test Patterns». Stub appartiene alla categoria dei test doubles — oggetti che sostituiscono componenti reali durante i test. L’obiettivo principale di uno stub è fornire all’unità testata dati prevedibili, eliminando l’incertezza dei sistemi esterni.
Principio di funzionamento — il test configura lo stub prima dell’esecuzione: «quando il metodo getUsers() viene chiamato, restituisci questa lista di utenti». Stub non contiene logica di business, non verifica l’ordine delle chiamate e non registra la cronologia. Si limita a stare al posto del componente reale e restituire ciò che gli è stato detto. Nel contesto del testing Android, ciò significa: il client OkHttp non effettua una richiesta reale al server, ma riceve una risposta da MockWebServer configurato come stub.
Quando utilizzare — gli stub sono ottimali per testare il livello UI (ViewModel, Presenter) e la logica di business (UseCase, Interactor), dove è necessario verificare la reazione a dati specifici: lista vuota, server ha restituito errore 500, token scaduto. Qualsiasi caso in cui il test richieda uno stato di input specifico è compito dello stub. Ogni scenario di test ha la propria configurazione di stub, rendendo i test leggibili e prevedibili.
Gerard Meszaros (2007) nel libro «xUnit Test Patterns» ha identificato cinque tipi di test doubles: dummy, stub, spy, mock, fake. Ogni tipo risolve il proprio problema. Dummy — viene passato ma non utilizzato. Stub — restituisce dati. Spy — registra chiamate. Mock — verifica comportamento. Fake — contiene logica semplificata. Comprendere questa classificazione aiuta lo sviluppatore a scegliere lo strumento giusto per ogni scenario di test.
Richieste di rete — lo scenario più comune per l’uso degli stub. L’applicazione effettua chiamate HTTP all’API e il test deve verificare la reazione a diverse risposte: JSON riuscito, errore 401 (non autorizzato), timeout, array vuoto. MockWebServer (OkHttp) su Android e URLProtocol (iOS) agiscono come stub, restituendo risposte HTTP predefinite senza connessione reale al server. Questo accelera i test da secondi a millisecondi.
Database — Room (Android) e CoreData (iOS) hanno varianti in memoria, ma la loro configurazione richiede comunque tempo. Stub al posto del repository restituisce elenchi di Entity preparati senza toccare il DB. Ciò è particolarmente efficace per testare ViewModel, dove è necessario verificare ordinamento, filtraggio o trasformazione dei dati. Il test viene eseguito in millisecondi indipendentemente dal volume di dati.
Servizi di sistema — LocationManager, SensorManager, SharedPreferences richiedono un dispositivo reale o un emulatore. Stub per LocationProvider restituisce coordinate predefinite, per SensorManager — valori fissi dell’accelerometro. Su iOS, l’equivalente è CLLocationManager con un’implementazione di delegato di test. Senza stub, questi test richiedono un dispositivo fisico con condizioni specifiche.
Filesystem e cache — caricamento immagini, caching delle risposte, lavoro con file di configurazione — tutte queste operazioni dipendono dallo stato del disco. Stub per FileManager o ImageCache restituisce successo/errore senza leggere file reali. Ciò elimina falsi fallimenti dei test dovuti a discrepanze di percorsi o diritti di accesso su diverse macchine di sviluppo.
Divisione delle responsabilità — tre tipi di test doubles risolvono compiti diversi. Stub: «dammi dati». Mock: «verifica che sono stato chiamato». Fake: «funziono come quello reale, solo più semplice». La differenza è fondamentale per la leggibilità dei test: se un test usa mock dove serve stub, è sovraccarico di chiamate verify non correlate allo scenario testato.
| Caratteristica | Stub | Mock | Fake |
|---|---|---|---|
| Scopo | Fornire dati | Verificare interazione | Implementazione semplificata |
| Logica | No | No | Sì (ma semplificata) |
| Verifica | No | Sì (verify) | Indiretta (tramite stato) |
| Flessibilità | Bassa — risposte fisse | Media | Alta — logica si adatta |
| Velocità | Massima | Alta | Media |
| Esempio | MockWebServer restituisce JSON | Mockito.verify(repository).save() | InMemoryRepository con HashMap |
Regola pratica — se il test verifica quali dati ha ricevuto il componente testato — usa stub. Se il test verifica se il componente ha chiamato il metodo della dipendenza con gli argomenti corretti — usa mock. Se vuoi semplicemente sostituire un DB con una tabella hash — questo è fake. Mescolare i tipi in un singolo test lo rende fragile: quando l’implementazione cambia, dovrai riscrivere sia la logica di stub che quella di verify.
Stub con verify — un errore comune in cui lo sviluppatore configura uno stub e poi aggiunge verify(stub).method(). Stub per definizione non dovrebbe essere verificato — per questo c’è mock. Se devi verificare che un metodo sia stato chiamato con argomenti specifici, usa Mockito.mock() invece di Mockito.stub(). Questa separazione mantiene chiara l’intenzione del test per gli altri sviluppatori.
MockWebServer — una libreria OkHttp per creare stub HTTP su Android e JVM. Avvia un server HTTP locale su una porta specificata che intercetta le richieste del client OkHttp e restituisce risposte predefinite. La configurazione richiede tre righe: creare il server, accodare la risposta, avviarlo. Il test può accodare sequenzialmente più risposte per scenari con paginazione o tentativi.
class UserRepositoryTest {
private val server = MockWebServer()
fun setup() {
server.start(8080)
val client = OkHttpClient.Builder()
.readTimeout(1, TimeUnit.SECONDS)
.build()
}
fun test_user_list_success() {
val json = "[{\"id\":1,\"name\":\"Alice\"}]"
server.enqueue(MockResponse()
.setBody(json)
.setResponseCode(200)
)
val result = repository.getUsers()
assertEquals(1, result.size)
}
fun teardown() {
server.shutdown()
}
}
MockK — un’alternativa a Mockito per Kotlin con supporto di prima classe per coroutine, funzioni di estensione e classi sealed. Gli stub in MockK vengono creati tramite coEvery (per funzioni suspend) e every (per funzioni regolari). A differenza di MockWebServer, MockK stub singoli metodi di dipendenza anziché l’intero livello HTTP. Ciò è comodo per i test unitari di UseCase o Interactor, dove le dipendenze sono astrazioni di repository.
interface UserRepository {
suspend fun getUsers(): List<User>
}
class GetUsersUseCaseTest {
private val repo = mockk<UserRepository>()
private val useCase = GetUsersUseCase(repo)
fun test_empty_list() = runTest {
coEvery { repo.getUsers() } returns emptyList()
val result = useCase.invoke()
assertTrue(result.isEmpty())
coVerify(exactly = 1) { repo.getUsers() }
}
}
Best practice — per i test di integrazione usa MockWebServer (intercetta HTTP reale), per i test unitari usa MockK (stub interfacce). Non stubbare ciò che non stai testando: se il test verifica il Repository, non stubbare il client OkHttp al suo interno — usa un vero MockWebServer a livello HTTP. Questa regola mantiene i test pertinenti e riduce la fragilità durante il refactoring.
Protocolli Swift come stub — nell’approccio nativo iOS, lo stub viene implementato sostituendo una struttura di test che è conforme al protocollo della dipendenza. Invece di un NetworkService reale, il test riceve un StubNetworkService che restituisce dati fissi. Swift è un linguaggio a tipizzazione statica, quindi lo stub deve essere conforme allo stesso protocollo del servizio reale. Il compilatore garantisce che lo stub implementi tutti i metodi richiesti.
protocol NetworkServiceProtocol {
func fetchUsers() async throws -> [User]
}
struct StubNetworkService: NetworkServiceProtocol {
let result: Result<[User], Error>
func fetchUsers() async throws -> [User] {
try result.get()
}
}
final class UsersViewModelTests: XCTestCase {
func test_success_state() async {
let stub = StubNetworkService(
result: .success([User(name: "Alice")])
)
let vm = UsersViewModel(service: stub)
await vm.load()
XCTAssertEqual(vm.users.count, 1)
}
}
OCMock per Objective-C — una libreria per creare stub e mock in progetti iOS legacy. OCMock supporta metodi stub con argomenti e valori di ritorno. I progetti moderni in Swift preferiscono un approccio basato su protocolli con stub manuali — questo dà controllo su ogni metodo e non richiede dipendenze esterne. OCMock rimane un’opzione per progetti dove protocollizzare tutte le dipendenze non è economicamente fattibile.
URLProtocol per stub HTTP — un meccanismo di sistema in iOS per intercettare le richieste di rete tramite una sottoclasse di URLProtocol. Il test registra un URLProtocol personalizzato che intercetta URLSession e restituisce risposte stub. Il vantaggio rispetto agli stub manuali: non è necessario modificare l’architettura dell’app — URLSession rimane reale, ma i dati vengono sostituiti a livello di protocollo. Lo svantaggio: più difficile da debuggare rispetto a un servizio stub esplicito.
Domande frequenti
Stub restituisce dati predefiniti e non verifica se una chiamata è avvenuta. Mock verifica inoltre che il metodo sia stato chiamato con gli argomenti corretti (verify). Stub risponde alla domanda «cosa restituire», Mock risponde a «la chiamata è stata effettuata?». Usa stub per la verifica dello stato, mock per la verifica dell’interazione.
Fake è necessario quando il test richiede un’implementazione funzionante (anche se semplificata) — ad esempio, un database in memoria invece di Room. Stub è adatto per scenari singoli con dati predefiniti. Se ripeti lo stesso stub in 10 test — probabilmente hai bisogno di un Fake. Fake riduce la duplicazione perché la logica vive in una singola classe.
Su Android — MockK per oggetti Kotlin (object) supporta mockkObject(), inclusi i metodi statici delle classi Java tramite mockkStatic(). Su iOS — i metodi statici di Swift non possono essere stubbati direttamente; usa protocolli e DI per sostituire una chiamata static con un metodo di istanza di un protocollo. Gli stub statici sono debito tecnico e vanno evitati nel nuovo codice.
Usa MockWebServer (OkHttp) — funziona come un server HTTP locale che accoda risposte. Per Retrofit, sostituisci semplicemente l’URL di base con localhost:8080. Per Ktor, usa MockEngine — un meccanismo integrato per sostituire HttpStatement. Entrambi gli approcci funzionano senza internet reale e danno controllo completo su codice di stato, corpo e intestazioni della risposta.
Spy avvolge un oggetto reale e registra le chiamate, mentre Stub sostituisce completamente l’oggetto con risposte fisse. Spy consente l’uso parziale dell’implementazione reale (gli altri metodi funzionano così come sono), mentre stub no. Se devi verificare che un metodo sia stato chiamato ma parte della logica deve essere eseguita — usa spy, non stub.
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