Introdução: Por que testes sem mocks são como café sem cafeína?
\n\nOlá, colega! Se você programa em Kotlin e quer que seu código não quebre a cada segundo commit, mais cedo ou mais tarde você vai chegar aos testes. E nos testes, aos mock objects e fixtures. Sem eles, os testes de unidade viram testes de integração: você não testa uma classe, mas uma cadeia inteira de dependências. Se o banco de dados cair ou a API retornar 500, o teste falha, mesmo que seu código esteja impecável.
\n\nNeste artigo, vamos ver como usar corretamente Mocks e Fixtures em testes Kotlin para que seus testes sejam rápidos, confiáveis e digam a verdade, e não "tudo quebrou, conserte". Vamos nessa!
\n\nO que é um mock e por que você precisa dele em Kotlin?
\n\nUm mock object é uma simulação de um objeto real. Em vez de levantar um servidor ou banco de dados inteiro, criamos um "simulacro" que se comporta exatamente como determinamos. Isso permite isolar o código testado do mundo externo.
\n\nConceitos principais
\n- \n
- Mock — um objeto cujo comportamento dos métodos controlamos. Podemos definir o que ele retorna e verificar se foi chamado. \n
- Stub — um caso especial de mock que apenas retorna valores predefinidos. Não verifica chamadas. \n
- Fixture — um conjunto de dados ou estados pré-preparados necessários para executar um teste. Por exemplo, um usuário de teste ou configurações de banco. \n
No mundo Kotlin, existem dois grandes players: MockK (nativo do Kotlin) e Mockito (popular no Java). Vamos usar o MockK — ele se dá melhor com corrotinas, funções de extensão e sealed classes.
\n\nConfiguração do projeto: adicionando MockK e JUnit 5
\n\nPrimeiro, adicione as dependências no build.gradle.kts:
dependencies {\n testImplementation("io.mockk:mockk:1.13.8")\n testImplementation("org.junit.jupiter:junit-jupiter:5.10.0")\n}\n\n\nAgora podemos escrever testes. Vamos criar um serviço simples e testá-lo com mocks.
\n\nPrimeiro exemplo: simulando um repositório
\n\nImagine que temos um UserService que depende de UserRepository:
class UserService(private val repository: UserRepository) {\n fun getUserName(id: Long): String {\n val user = repository.findById(id)\n ?: throw IllegalArgumentException("User not found")\n return user.name\n }\n}\n\n\nAgora vamos escrever um teste usando um mock object para o repositório:
\n\nimport io.mockk.every\nimport io.mockk.mockk\nimport io.mockk.verify\nimport org.junit.jupiter.api.Test\nimport kotlin.test.assertEquals\nimport kotlin.test.assertFailsWith\n\nclass UserServiceTest {\n\n private val repository = mockk<UserRepository>()\n private val service = UserService(repository)\n\n @Test\n fun `deve retornar nome do usuário quando usuário existe`() {\n // Arrange\n every { repository.findById(1L) } returns User(1L, "Alice")\n\n // Act\n val name = service.getUserName(1L)\n\n // Assert\n assertEquals("Alice", name)\n verify { repository.findById(1L) }\n }\n\n @Test\n fun `deve lançar exceção quando usuário não for encontrado`() {\n // Arrange\n every { repository.findById(any()) } returns null\n\n // Act & Assert\n assertFailsWith<IllegalArgumentException> {\n service.getUserName(99L)\n }\n }\n}\n\n\nObserve: every define o comportamento do mock, verify verifica se o método foi chamado. Essa é a base dos mocks e fixtures em testes Kotlin.
Fixtures: preparando dados com antecedência
\n\nQuando há muitos testes, duplicar a criação de dados é uma má prática. É aí que entram as fixtures. Em Kotlin, é conveniente