Testes Unitários de API REST com Kotlin: Configuração
Ao concluir esta aula, seremos capazes de:
- Compreender como podemos remover código duplicado da nossa suíte de testes.
- Configurar o sistema sob teste (System Under Test — SUT) utilizando a configuração standalone do Spring MVC Test.
Nota — Este webinar assume que:
- Você sabe executar testes em Kotlin com Maven ou Gradle (utilizando Gradle 9.8.2 com Kotlin DSL).
- Você sabe escrever classes de teste com JUnit Jupiter e Kotlin (JUnit Jupiter 6.0.3 e Kotlin 2.4.21).
- Você sabe criar dublês de teste (test doubles) com MockK (MockK 1.14).
- Você sabe configurar o framework Spring MVC Test utilizando a configuração standalone (Spring Boot 4.1.2).
- Você sabe configurar o framework Spring MVC Test ao escrever testes unitários para uma API REST.
Obtenha o código-fonte de referência no GitHub
Removendo Código Duplicado da Nossa Suíte de Testes
Não queremos adicionar código duplicado à nossa suíte de testes porque isso torna nossos testes difíceis de escrever e de manter. Durante esta aula, aprenderemos duas técnicas que nos ajudam a remover código duplicado da suíte de testes quando escrevemos testes unitários para controllers Spring MVC em Kotlin 2.4.21 e Spring Boot 4.1.2.
A seguir, veremos como criar os componentes que sobrescrevem a configuração padrão fornecida pelo framework Spring MVC Test.
Criando os Componentes Adicionais
Como vimos anteriormente, se criarmos os componentes necessários diretamente dentro das nossas classes de teste, introduziremos código duplicado na suíte de testes, pois é muito provável que múltiplas classes de teste utilizem exatamente a mesma configuração de infraestrutura web. Por essa razão, devemos criar uma classe Object Mother responsável por instanciar e configurar esses componentes. Podemos construir nossa classe Object Mother seguindo estes passos:
Primeiro, devemos criar uma nova classe e garantir que ela não possa ser instanciada externamente declarando seu construtor primário como private. Após criarmos essa classe, seu código-fonte inicial é o seguinte:
|
|
Segundo, devemos escrever as funções fábrica estáticas (dentro de um companion object) que criam e configuram os componentes necessários. Precisamos adicionar três funções fábrica à nossa classe Object Mother:
- A função
fixedLocaleResolver()retorna um novo objetoFixedLocaleResolverque sempre resolve o locale paraLocale.ENGLISH. É uma excelente prática fixar o locale porque isso garante que o ambiente do sistema operacional ou do servidor de CI onde nossos testes são executados não cause falsos positivos (isto é, falhas de teste provocadas por diferenças de idioma/formatação local). - A função
objectMapper()cria e configura um novo objetoObjectMapper. Isolamos essa lógica em uma função dedicada porque também a reutilizaremos quando precisarmos serializar objetos para documentos JSON enviados no corpo das requisições HTTP ao sistema sob teste. - A função
objectMapperHttpMessageConverter()cria um novo objetoMappingJackson2HttpMessageConvertercapaz de ler e escrever documentos JSON utilizando oObjectMapperconfigurado com Jackson (incluindo o suporte aJavaTimeModule,Jdk8ModuleekotlinModule()para data classes do Kotlin).
Após adicionarmos essas funções fábrica ao companion object da nossa classe Object Mother, o código-fonte completo de WebTestConfig fica assim:
|
|
Vamos avançar e descobrir como podemos criar uma classe Request Builder encarregada de construir e enviar requisições HTTP para o sistema sob teste.
Criando uma Classe Request Builder
Quando escrevemos testes para uma aplicação web real ou para uma API REST, rapidamente percebemos que cada método de teste constrói uma nova requisição HTTP e a envia ao sistema sob teste. Essa repetição direta dentro dos testes é prejudicial porque código duplicado de montagem de requisições (URIs, cabeçalhos, Content-Type, serialização de payload) torna a suíte difícil de escrever, ler e manter.
Podemos resolver esse problema utilizando classes Request Builder. Uma classe Request Builder é uma classe auxiliar de teste que cumpre duas condições essenciais:
- Contém funções que criam e enviam requisições HTTP ao sistema sob teste utilizando um objeto
MockMvc(seja viaMockMvcRequestBuilderstradicional ou via MockMvc Kotlin DSL commockMvc.get,mockMvc.post,mockMvc.putemockMvc.delete). - Cada função retorna um objeto
ResultActions(ouResultActionsDslna DSL idiomática do Kotlin), permitindo que o método de teste escreva asserções fluentes sobre a resposta HTTP retornada.
Quando queremos escrever uma nova classe Request Builder em Kotlin, devemos seguir estes passos:
- Criar uma nova classe (
TaskCrudRequestBuilder). - Adicionar um construtor primário à classe criada.
- Garantir que o construtor primário declare uma propriedade
private val mockMvc: MockMvc(sem expor getter ou setter público). Nossa classe Request Builder utilizará essa propriedade interna sempre que precisar criar e enviar requisições HTTP ao sistema sob teste.
Após criarmos a estrutura inicial da nossa classe Request Builder, seu código-fonte fica da seguinte forma:
|
|
Nota:
Neste momento, essa classe Request Builder ainda não faz nada útil porque ainda não escrevemos as funções que criam e disparam as requisições HTTP para o sistema sob teste. Adicionaremos essas funções passo a passo nas próximas aulas, à medida que implementarmos os testes unitários para cada operação da nossa API REST.
Leitura Adicional:
Agora que criamos os componentes que nos ajudam a eliminar código duplicado ao escrever testes unitários para uma API REST, vamos aprender a configurar o sistema sob teste utilizando esses componentes.
Configurando o Sistema Sob Teste
Antes de configurarmos o sistema sob teste (System Under Test — SUT), precisamos decidir qual será o tamanho da unidade testada (unit size). Quando desenvolvedores escrevem testes unitários, é muito comum adotarem a menor unidade possível: uma única classe isolada, substituindo todas as dependências diretas da classe testada por dublês de teste (test doubles).
A figura a seguir ilustra o tamanho da unidade testada quando utilizamos essa abordagem tradicional focada apenas na classe do controller:

O problema dessa abordagem é que podemos acabar escrevendo testes unitários superficiais (shallow unit tests), especialmente quando a classe testada não possui lógica de negócios própria. Por exemplo, se estivermos escrevendo testes unitários para um controller Spring MVC e substituirmos seu serviço (TaskCrudService) por um mock, não conseguiremos testar nenhuma regra de negócio. Só conseguiremos garantir que:
- Nossa requisição HTTP é roteada e processada pela função correta do controller.
- Nossas regras de validação de entrada estão funcionando conforme o esperado.
- Os dados corretos são repassados para a função esperada do serviço.
- O sistema sob teste retorna a resposta HTTP esperada a partir do valor simulado pelo serviço.
Desta vez, faremos algo diferente e muito mais expressivo: vamos aumentar o tamanho da unidade testada para abranger tanto o controller quanto a camada de serviço (TaskCrudController + TaskCrudService), substituindo apenas as dependências externas do nosso componente de serviço por dublês de teste criados com MockK 1.14. O objetivo desse experimento é demonstrar como essa abordagem nos ajuda a escrever testes unitários mais significativos e resilientes a refatorações internas para uma API REST com Spring.
A figura a seguir ilustra o tamanho da unidade testada quando adotamos essa abordagem de teste unitário de funcionalidade (feature unit test):

Podemos agora configurar o sistema sob teste seguindo quatro passos:
Primeiro, devemos criar uma nova classe de teste (TaskCrudControllerTest) e garantir que suas funções de teste sejam executadas quando rodarmos nossa suíte de testes unitários com JUnit Jupiter 6.0.3 (anotando a classe com @Tag("unitTest") e @DisplayName). Após criarmos nossa classe de teste, seu código-fonte inicial é o seguinte:
|
|
Segundo, devemos adicionar três propriedades private lateinit var à nossa classe de teste:
- A classe
PersonFinderbusca dados de pessoas no banco de dados. - A classe
TaskRepositoryfornece operações CRUD para tarefas (tasks). - A classe
TaskCrudRequestBuildercria requisições HTTP e envia essas requisições ao sistema sob teste.
Após adicionarmos essas propriedades à nossa classe de teste, seu código-fonte fica assim:
|
|
Terceiro, devemos adicionar uma função de configuração (setup) à nossa classe de teste e anotá-la com @BeforeEach para garantir que ela seja invocada antes da execução de cada função de teste. Após adicionarmos essa função à nossa classe de teste, seu código-fonte fica da seguinte forma:
|
|
Quarto, devemos implementar nossa função configureSystemUnderTest() seguindo estes passos:
- Criar mocks para
PersonFindereTaskRepositoryutilizando a funçãomockk()do MockK 1.14 e armazenar os objetos mock criados nas propriedadespersonFindererepository. - Criar uma nova instância real de
TaskCrudService(injetando os mockspersonFindererepository) e armazená-la em uma variável localservice. - Criar um novo objeto
MockMvcutilizando a configuração standalone (MockMvcBuilders.standaloneSetup(...)) com uma instância real deTaskCrudController(service), registrando o@ControllerAdvice(TaskErrorHandler()), oLocaleResolverfixo (WebTestConfig.fixedLocaleResolver()) e o conversor HTTP Jackson (WebTestConfig.objectMapperHttpMessageConverter()), armazenando o resultado na variável localmockMvc. - Criar um novo objeto
TaskCrudRequestBuilder(mockMvc)e armazená-lo na propriedaderequestBuilder.
Após implementarmos nossa função de configuração, o código-fonte completo da nossa classe de teste fica assim:
|
|
Agora sabemos como configurar o sistema sob teste utilizando a configuração standalone do Spring MVC Test em conjunto com Kotlin e MockK. Vamos resumir o que aprendemos nesta aula.
Resumo
Esta aula nos ensinou três pontos fundamentais:
- Podemos configurar o sistema sob teste sem escrever código duplicado utilizando uma classe Object Mother (
WebTestConfig) que cria e configura todos os componentes de infraestrutura adicionados ao sistema sob teste. - Podemos enviar requisições HTTP ao sistema sob teste sem espalhar código duplicado pelos testes utilizando uma classe Request Builder (
TaskCrudRequestBuilder). - Quando queremos configurar o sistema sob teste utilizando a modalidade standalone do Spring MVC Test, invocamos o método
standaloneSetup()da classeMockMvcBuilderspassando o controller real e encadeando a configuração deControllerAdvice,LocaleResolvereHttpMessageConverter.