Spy (espião) — um objeto de teste que envolve uma instância real e registra informações sobre cada chamada: quais métodos foram chamados, com quais argumentos e quantas vezes. Ao contrário de um mock, um spy usa a implementação real do objeto envolvido — as chamadas passam pelo código real, e o spy apenas registra os fatos. Após a execução do teste, o desenvolvedor verifica os registros do espião: “O método sendAnalytics foi chamado três vezes?”. Saiba mais no guia de testes Android.
Principais conclusões
Spy — é um invólucro em torno de um objeto real que intercepta todas as chamadas de métodos e as registra. A lógica real do objeto é executada: se o método salva dados, calcula um valor ou faz uma requisição — tudo acontece normalmente. Além disso, o spy registra metadados: nome do método, argumentos, número de chamadas, tempo de execução. O termo faz parte da classificação de Meszaros (2007) e é descrito em detalhes no artigo de Martin Fowler “Mocks Aren’t Stubs”.
Diferença principal de um Mock — um mock substitui completamente o objeto por um stub de teste; todos os métodos não fazem nada por padrão. Um spy envolve um objeto existente: todos os métodos funcionam normalmente por padrão, mas também são registrados. Essa distinção é fundamental: um mock isola o código testado da realidade, enquanto um spy preserva a realidade e permite observá-la. A escolha entre eles depende do que está sendo testado.
Um spy é a escolha certa — se o código testado modifica o estado de um objeto real, e o teste deve verificar tanto o resultado (estado) quanto garantir que as chamadas foram feitas na ordem correta. Um mock não é adequado porque não executa a implementação real. Um stub não é adequado porque não registra chamadas. Um spy é o único test double que preserva a lógica real e fornece informações sobre as chamadas.
Mock — isolamento completo. Se o teste não deve depender da implementação do objeto real (por exemplo, um banco de dados ou cliente de rede), use um mock. Um mock garante que nenhuma chamada atinja o componente real. É seguro e previsível. A desvantagem: um mock não executa lógica real, portanto, se o código testado depende de um valor de retorno, ele deve ser configurado explicitamente via when/stub.
Spy — lógica real + observação. Se o código testado interage com um objeto cuja lógica é importante para o teste, e não apenas dados — use um spy. Por exemplo, um AnalyticsTracker que coleta eventos e os envia periodicamente. O teste verifica que os eventos foram adicionados ao buffer e, após o envio, o buffer foi limpo. Um mock não pode verificar isso porque não executa a lógica real do tracker.
| Cenário | Spy | Mock |
|---|---|---|
| Lógica real necessária | Sim | Não (stub) |
| Verificação de chamadas | Sim (quantidade, argumentos) | Sim (quantidade, argumentos) |
| Stubbing parcial | Sim (alguns métodos spy, outros stub) | Não (todos os métodos são stubs) |
| Risco de efeitos colaterais | Alto (código real) | Nulo |
| Velocidade | Menor (lógica real) | Maior (stubs) |
| Legibilidade | Menor (mais difícil entender o que é real) | Maior (tudo é explícito) |
Spy para tudo — usar um spy em vez de um mock para todos os testes é um erro. Um spy executa código real que pode ter efeitos colaterais: escrever em um arquivo, enviar HTTP, alterar o estado global. Se o módulo testado chama um método de um objeto spy que faz uma requisição HTTP, o teste se torna um teste de integração, não unitário. Regra: se um spy envolve um objeto com operações de E/S — não é mais um teste unitário. Use mocks para isolar E/S e spies apenas para objetos em memória sem efeitos externos.
Mockito.spy() — a forma clássica de criar um espião em projetos Java/Kotlin. spy() recebe um objeto real e retorna um invólucro. Todas as chamadas são delegadas ao objeto real por padrão e os resultados são registrados. Após o teste, você pode usar verify() para verificar o número de chamadas e os argumentos. Para métodos que devem retornar dados de teste, usa-se doReturn/when — isso é chamado de “partial mocking”.
class AnalyticsReporterTest {
private val realTracker = AnalyticsTracker()
private val spyTracker = Mockito.spy(realTracker)
fun test_event_tracked() {
val event = AnalyticsEvent("login")
spyTracker.track(event)
Mockito.verify(spyTracker).track(event)
assertEquals(1, spyTracker.getBufferedCount())
}
fun test_track_with_exception() {
Mockito.doThrow(RuntimeException("network"))
.when(spyTracker).flush()
spyTracker.track(AnalyticsEvent("login"))
assertTrue(spyTracker.hasPendingEvents())
}
}
MockK.spyk() — uma alternativa para projetos Kotlin com melhor suporte para corrotinas e classes seladas. MockK.spyk() cria um espião, análogo ao Mockito.spy(). Suporta coVerify para funções suspend e every para stubbing parcial. Ao contrário do Mockito, MockK não suporta spies para classes final (todas as classes em Kotlin são final por padrão) — você precisa abrir a classe (open) ou usar uma interface.
class LoginUseCaseTest {
private val realRepo = UserRepository()
private val spyRepo = spyk(realRepo)
private val useCase = LoginUseCase(spyRepo)
fun test_login_calls_save() = runTest {
every { spyRepo.getUser(any()) } returns User("test")
val result = useCase.login("test", "pass")
coVerify { spyRepo.saveLoginTime(any()) }
assertTrue(result.isSuccess)
}
}
Stubbing parcial — uma técnica poderosa mas perigosa. Você pode criar um spy de um objeto e sobrescrever (stub) apenas alguns métodos, deixando os restantes reais. Exemplo: um repositório spy onde getUser() retorna dados de teste, enquanto saveUser() realmente salva em uma lista em memória. Isso permite combinar os benefícios de stubs (dados controlados) e spies (lógica real). A desvantagem: a legibilidade do teste é prejudicada — não é óbvio quais métodos são reais e quais são stubs.
OCMock para Objective-C — uma biblioteca que suporta a criação de objetos spy via niceMock. OCMock intercepta chamadas de métodos usando o runtime do Objective-C e as registra. Após o teste, verify é chamado. OCMock suporta spies para qualquer objeto (todos os métodos em Objective-C são dinâmicos), o que lhe dá vantagem sobre Swift, onde spies são possíveis apenas através de protocolos.
// Criação de spy para um objeto real
AnalyticsTracker *realTracker = [[AnalyticsTracker alloc] init];
AnalyticsTracker *spy = [OCMockObject partialMockForObject:realTracker];
// Execução do teste
[spy trackEvent:@"login"];
// Verificação
[[spy verify] trackEvent:@"login"];
XCTAssertEqual([realTracker eventCount], 1);
Spy baseado em protocolo em Swift — Swift não tem reflexão de runtime como Objective-C, então spies são criados manualmente. Uma estrutura de teste implementa um protocolo e, internamente, chama o objeto real enquanto registra as chamadas simultaneamente. Isso requer mais código, mas é totalmente controlável e type-safe. Spies manuais não requerem bibliotecas externas e não usam runtime — tudo é verificado em tempo de compilação.
protocol AnalyticsProtocol {
func trackEvent(name: String)
}
final class SpyAnalytics: AnalyticsProtocol {
private let real: AnalyticsProtocol
private var events: [String] = []
init(real: AnalyticsProtocol) {
self.real = real
}
func trackEvent(name: String) {
events.append(name)
real.trackEvent(name: name)
}
func verifyTracked(name: String) -> Bool {
return events.contains(name)
}
}
Quando usar OCMock vs spy manual — para código Objective-C, use OCMock (menos boilerplate). Para Swift, prefira spies manuais através de protocolos. Um spy manual dá controle total sobre o registro de chamadas, não requer reflexão e funciona com tipos valor (structs). A única desvantagem é que você precisa manter o código da classe spy sincronizado com o protocolo ao adicionar novos métodos.
Verificação de analítica — o caso de uso mais comum para spies. No código de produção, as chamadas de analítica estão espalhadas por todo o aplicativo: login, logout, compra, erro. O teste cria um invólucro spy para AnalyticsTracker, executa um cenário (login, ver produto, adicionar ao carrinho, comprar) e verifica se todos os eventos necessários foram enviados na ordem correta. Um mock não é adequado porque AnalyticsTracker contém lógica de buffering e envio.
Temporizadores e agendadores — testar código que usa Handler (Android) ou Timer (iOS) é difícil devido ao tempo real. Um invólucro spy para Scheduler registra quais tarefas foram agendadas e com que atraso. O teste cria um spy do Handler real, executa uma ação e verifica se Handler.postDelayed(runnable, delay) foi chamado com o atraso correto. A tarefa real não é executada — o spy intercepta e registra a chamada.
Logging e informação de depuração — em produção, os logs podem estar desativados ou escritos em um arquivo. Um invólucro spy para Logger registra todas as mensagens em uma lista em memória que o teste verifica após a execução. Isso permite verificar se a mensagem correta foi escrita em caso de erro sem poluir o console. Spies manuais para Logger são especialmente úteis no iOS, onde OSLog não tem API de teste.
Verificação de ordem de chamadas — alguns cenários exigem uma ordem estrita de operações: abrir conexão, enviar dados, fechar conexão. Mockito permite verificar a ordem através de InOrder.verify(). Um spy faz o mesmo mas preserva a execução real. Se tanto a ordem quanto o resultado de cada etapa (a conexão realmente abriu) importam — use um spy, não um mock.
Perguntas frequentes
Spy envolve um objeto real e executa sua lógica, além de registrar chamadas. Mock substitui completamente o objeto por um stub — nenhuma lógica real é executada. Um spy preserva o comportamento, um mock não. Escolha um spy quando o trabalho real do objeto for importante; escolha um mock quando precisar isolar o teste de uma dependência externa.
Quando um invólucro spy leva a operações reais de E/S. Se um spy envolve um objeto que escreve em um arquivo, envia HTTP ou lê do disco — o teste deixa de ser unitário. Segundo caso: o teste verifica apenas o valor de retorno sem se importar com chamadas — aqui um stub é suficiente e um spy é redundante. Terceiro: o código depende do estado interno do spy — isso é um teste frágil.
Sim, através de spyk() — o equivalente a Mockito.spy(). MockK.spyk() cria um espião em torno de um objeto real, suporta every para stubbing parcial e coVerify/coroutinesVerify para funções suspend. Limitação: não funciona com classes final (precisa de open ou interface). Para classes Java, MockK também suporta spyk() mas requer a anotação @MockKJvmInline.
Tecnicamente, não. Mock é um stub que não contém implementação real. Spy, por definição, envolve um objeto real. No Mockito, você não pode transformar um mock em spy. Mas você pode fazer o contrário: criar um spy e sobrescrever alguns métodos via doReturn/when (partial mocking). Isso dá comportamento semelhante a um mock para métodos selecionados do objeto spy.
Sim, requer. Em Swift, não há proxy dinâmico como em Java/Kotlin. Para criar um spy, é necessário um protocolo que tanto a classe de produção quanto a classe spy implementam. Um spy baseado em protocolo em Swift é uma implementação manual que recebe um objeto real, delega chamadas a ele e registra metadados. Alternativa: a biblioteca Cuckoo, que gera classes spy através do SourceKit.
Resumo
Vamos desenvolver um aplicativo móvel chave na mão
A IT Sectr cria aplicativos para iOS e Android para startups e empresas desde 2017. Nós vamos aconselhá-lo e propor a melhor solução.
Leia também