Spy — o que é, Mockito.spy() e verificação de chamadas

Autor: IT Sectr Publicado: 2026-04-10 Tempo de leitura: 9 min

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 — objeto que registra chamadas de métodos de uma implementação real sem substituí-la
  • Verificação — após o teste, um spy permite verificar quantas vezes e com quais argumentos um método foi chamado
  • Mockito.spy() — cria um espião no Android para objetos Java/Kotlin reais
  • Partial mocking — um spy pode ser combinado com stubs: interceptar alguns métodos, deixar outros passarem
  • iOS — OCMock (Objective-C) e espiões manuais através de protocolos em Swift

O que é um Spy e como é diferente de um Mock?

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.

Quando um Spy é a escolha certa

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.

Spy vs Mock: quando usar um espião

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árioSpyMock
Lógica real necessáriaSimNão (stub)
Verificação de chamadasSim (quantidade, argumentos)Sim (quantidade, argumentos)
Stubbing parcialSim (alguns métodos spy, outros stub)Não (todos os métodos são stubs)
Risco de efeitos colateraisAlto (código real)Nulo
VelocidadeMenor (lógica real)Maior (stubs)
LegibilidadeMenor (mais difícil entender o que é real)Maior (tudo é explícito)

Antipadrão: spy para tudo

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() e spy no MockK no Android

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

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

kotlin
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 via Spy

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.

Implementação de Spy no iOS com OCMock e protocolos

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.

objective-c
// 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.

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

Cenários típicos de uso de Spy

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 vs Mock: Qual é a principal diferença?

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 Spy é uma má escolha?

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.

MockK suporta spy?

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.

É possível fazer um Spy a partir de um Mock?

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.

Spy em Swift requer um protocolo?

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

  • Spy — invólucro em torno de um objeto real que registra todas as chamadas de métodos sem substituir a lógica
  • Diferença de Mock — um spy executa código real, um mock substitui por um stub
  • Mockito.spy() — para Java/Kotlin, envolve um objeto real com suporte a verify e partial mock
  • MockK.spyk() — equivalente em Kotlin com suporte a corrotinas, classes seladas e coVerify
  • iOS — OCMock para Objective-C, spies manuais baseados em protocolo para Swift
  • Cenários principais — verificação de analítica, temporizadores, logs e ordem de chamadas
  • Cuidado — um spy com operações de E/S transforma um teste unitário em teste de integração

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.

Discutir o projeto

Leia também