Test Doubles são objetos substitutos usados em testes unitários no lugar de dependências reais. O termo foi introduzido por Gerard Meszaros no livro “xUnit Test Patterns” (2007) como um conceito geral para Mock, Stub, Fake, Spy e Dummy. De acordo com Martin Fowler (2024), os Test Doubles permitem isolar o componente sob teste do seu ambiente, tornando os testes determinísticos, rápidos e independentes de serviços externos.
Principais pontos
Test Doubles é um termo da indústria automotiva (dublê), transferido para o desenvolvimento de software. Assim como um dublê substitui um ator em uma cena perigosa, um Test Double substitui um componente real em um cenário de teste. Isso é necessário quando a dependência real está indisponível, é lenta, não determinística ou tem efeitos colaterais.
O conceito de Test Double abrange cinco tipos específicos, cada um resolvendo sua própria tarefa. A tipologia de Meszaros é canônica e usada em todos os guias modernos de teste. A diferença entre os tipos está no grau de controle e verificação: desde o simples preenchimento de parâmetros (Dummy) até a verificação completa da sequência de chamadas (Mock).
O principal propósito dos Test Doubles é isolar o módulo sob teste. No desenvolvimento mobile, as dependências reais incluem servidores de API, bancos de dados, sistema de arquivos, sensores do dispositivo e serviços do sistema (LocationManager, Camera, Bluetooth). Usar esses componentes diretamente torna os testes lentos, frágeis e dependentes do ambiente. De acordo com o Google Testing Blog (2023), testes unitários bem isolados são executados em milissegundos, enquanto testes de integração são executados em segundos e minutos.
A classificação de Gerard Meszaros inclui cinco tipos de Test Doubles, que diferem em comportamento e propósito. Entender a diferença entre eles é a base de testes unitários competentes.
Dummy é um objeto que é passado para o método sob teste mas nunca é usado. Dummy é necessário apenas para satisfazer a assinatura do método. Em Kotlin, isso é frequentemente null, emptyList() ou um objeto com stubs. Dummy não deve conter nenhuma lógica — se for chamado, o teste deve falhar.
Fake é uma implementação simplificada mas funcional de uma interface. Ao contrário de Mock e Stub, Fake contém lógica de negócio real, mas de forma simplificada. Um exemplo clássico é o InMemoryUserRepository, que armazena dados em um HashMap em vez de um banco de dados. Fake é usado quando você precisa testar lógica que depende de estado, mas sem a sobrecarga da infraestrutura real.
| Tipo | Propósito | Exemplo |
|---|---|---|
| Dummy | Preencher um parâmetro | null, objeto vazio |
| Fake | Implementação simplificada funcional | InMemoryRepository |
| Stub | Retornar um valor fixo | when(api.getUser()).thenReturn(user) |
| Spy | Registrar chamadas para verificação | verify(spy).save(user) |
| Mock | Verificar interação | verify(mock).sendEmail(email) |
Stub retorna valores predefinidos para chamadas específicas. Stub não verifica se foi chamado — ele simplesmente fornece dados. Em Mockito, Stub é criado via when(method).thenReturn(value). Stub é ideal para testes quando você precisa que uma dependência retorne um valor específico, mas o fato da chamada em si não é importante.
Spy é um invólucro em torno de um objeto real que registra todas as chamadas para verificação posterior. Ao contrário de Mock, Spy delega chamadas ao objeto real mas permite verificar que elas ocorreram. Em Mockito, Spy é criado via spy(realObject). Spy é útil para mocking parcial, quando você quer usar um objeto real mas verificar algumas chamadas.
Mock é um objeto com expectativas de chamada predefinidas. Mock verifica que métodos específicos foram chamados com argumentos específicos e em uma ordem específica. Ao contrário de Stub, Mock foca na verificação de comportamento em vez do retorno de dados. Mock é o tipo mais poderoso e mais usado de Test Double no desenvolvimento mobile.
A diferença entre Mock e Stub frequentemente causa confusão até mesmo entre desenvolvedores experientes. A principal diferença está no propósito: Stub verifica estado (state verification), Mock verifica comportamento (behavior verification).
Stub responde à pergunta: “o código retornou o resultado correto?”. Mock responde à pergunta: “o código chamou os métodos corretos com os argumentos corretos?”. No desenvolvimento mobile, Stub é usado quando o resultado importa (ex.: dados de um repositório), enquanto Mock é usado quando os efeitos colaterais importam (ex.: enviar email, escrever em um banco de dados).
// Stub: verificação de estado
every { repository.getUsers() } returns listOf(user)
val result = useCase.getUsers()
assertEquals(1, result.size)
// Mock: verificação de comportamento
every { analytics.logEvent("purchase") } returns Unit
useCase.purchase(item)
verify { analytics.logEvent("purchase") }
Exemplos práticos de todos os cinco tipos de Test Doubles em Kotlin usando MockK — a biblioteca de mocking mais popular para projetos Android.
class InMemoryUserRepository : UserRepository {
private val store = mutableMapOf<String, User>()
override fun save(user: User) {
store[user.email] = user
}
override fun findByEmail(email: String): User? {
return store[email]
}
}
class RegisterUseCaseTest {
private val api = mockk<AuthApi>()
private val repo = spyk(InMemoryUserRepository())
private val useCase = RegisterUseCase(api, repo)
fun `register user successfully`() = runTest {
// Stub: retornar resposta fixa de API
coEvery { api.register("test@test.com") } returns AuthResult.Success("token123")
val result = useCase.execute("test@test.com")
// Verify: verificar que o usuário foi salvo
verify { repo.save(any()) }
assertTrue(result.isSuccess())
}
}
data class Logger(val appContext: Context, val format: FormatType)
fun `test logger with dummy context`() {
// Dummy: Context não é usado dentro de Logger
val dummyContext = mockk<Context>()
val logger = Logger(dummyContext, FormatType.JSON)
assertEquals(FormatType.JSON, logger.format)
}
A escolha do tipo de Test Double depende do que exatamente está sendo testado: estado, comportamento ou integração. No desenvolvimento mobile Android e iOS, as seguintes recomendações foram estabelecidas.
Ao testar ViewModel, use Mock para dependências que produzem efeitos colaterais (repositórios, analytics, navegação) e Stub para dependências que retornam dados (clientes de API, ContentProvider). Isso permite verificar que a ViewModel lida corretamente com cenários de sucesso e erro.
No nível de Repository, prefira Fake (implementações de banco de dados em memória) e Stub (respostas fixas de API). Fake permite testar lógica de cache e modo offline sem configurar SQLite. Stub simula vários status HTTP: 200, 404, 500, timeout.
O uso incorreto de Test Doubles é uma das causas mais comuns de testes frágeis que quebram a cada refatoração.
O erro mais comum é mockar tudo. Se cada dependência em um teste é substituída por um Mock, o teste para de verificar o comportamento real. Mock deve ser usado apenas para dependências externas (rede, banco de dados, sistema de arquivos, serviços do sistema). Componentes internos da aplicação (Value Object, data class, utilitários simples) não devem ser substituídos.
O segundo erro é criar um Mock sem definir expectativas. Se um método é chamado sem every / when, Mock retorna um valor padrão (null, 0, false). Isso pode levar a testes falso-positivos, onde Mock silenciosamente retorna null e o teste interpreta isso como comportamento correto.
O terceiro erro é verificar cada chamada de cada Mock. Verify deve ser usado apenas para chamadas que são criticamente importantes do ponto de vista da lógica de negócio. A verificação excessiva torna os testes frágeis: alterar a ordem das chamadas no código de produção quebra os testes sem alterar o comportamento.
Perguntas frequentes
Stub retorna dados e verifica o estado (o que foi retornado), enquanto Mock verifica o comportamento (quais métodos foram chamados). Stub = “retorne X”, Mock = “verifique que Y foi chamado com o argumento Z”. Em testes reais, um mesmo objeto frequentemente atua como Stub e Mock simultaneamente.
Fake é preferível a Mock quando se testa lógica que depende de estado: cache, modo offline, transações. Fake (implementação em memória) permite testar esses cenários sem chamadas verify frágeis. Mock é mais adequado para verificar envio de dados: analytics, push, email.
Para projetos Android em Kotlin, MockK é recomendado. Ele suporta corrotinas, funções suspend, classes seladas e funções de extensão sem configuração adicional. Para projetos Java, Mockito continua sendo o padrão — a biblioteca mais popular com documentação extensa.
Para testar Kotlin Flow, use a biblioteca Turbine junto com MockK. Turbine simplifica a verificação de emissões de Flow: você pode verificar a ordem dos valores, a conclusão do fluxo e exceções. Stub para Flow retorna flowOf(value), Mock verifica que o Flow foi coletado.
Sim, mas no nível de respostas de API, não de componentes de UI. As bibliotecas MockWebServer (OkHttp) e WireMock permitem simular respostas HTTP em testes de UI. Os próprios componentes de UI (Compose, SwiftUI Views) não devem ser substituídos — seu comportamento é testado através de testes de captura de tela e Espresso.
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