Testes de Integração de API REST: Configuração

Ao concluirmos esta aula, entenderemos como podemos eliminar código de configuração duplicado quando configuramos nossos testes de integração para uma API REST.

[!NOTE] Esta aula assume que:

Obtenha o código-fonte no GitHub

Configurando Nossos Testes de Integração

Podemos configurar nossos testes de integração seguindo estes passos:

Primeiro, como queremos eliminar código de configuração duplicado dos nossos testes de integração, precisamos criar uma anotação customizada (meta-anotação composta) que contenha a configuração compartilhada por todos os nossos testes de integração de API REST. Podemos criar essa anotação seguindo estes passos:

  1. Criar uma nova classe de anotação (annotation class WebIntegrationTest).
  2. Garantir que nossa anotação customizada possa ser aplicada a uma classe de teste (@Target(AnnotationTarget.CLASS)).
  3. Garantir que nossa anotação customizada seja armazenada na saída binária (bytecode compilado) e fique visível para reflexão em tempo de execução (@Retention(AnnotationRetention.RUNTIME)).
  4. Garantir que nossa anotação customizada seja incluída na documentação gerada a partir das nossas classes de teste (@MustBeDocumented).
  5. Habilitar a autoconfiguração do framework Spring MVC Test (@AutoConfigureMockMvc). Quando habilitamos essa funcionalidade no Spring Boot 4.1.2, o framework Spring TestContext cria e autoconfigura uma instância pronta de MockMvc (totalmente compatível com a MockMvc Kotlin DSL), que pode ser injetada diretamente em nossas classes de teste.
  6. Garantir que nossa anotação customizada utilize a configuração que criamos anteriormente quando escrevemos testes de integração para um repositório Spring (@DatabaseIntegrationTest).

Depois de criarmos a anotação @WebIntegrationTest, seu código-fonte fica da seguinte forma:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
package com.cleantestautomation.tb2020.common

import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc

@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
@MustBeDocumented
@AutoConfigureMockMvc
@DatabaseIntegrationTest
annotation class WebIntegrationTest

[!TIP] Composição de Meta-Anotações no Spring Boot:

Observe como a anotação @WebIntegrationTest herda toda a infraestrutura definida em @DatabaseIntegrationTest:

  1. @SpringBootTest: Carrega o contexto completo da aplicação Spring Boot (ApplicationContext).
  2. @ActiveProfiles("integrationTest"): Ativa o perfil que direciona a aplicação para a URL dinâmica do Testcontainers PostgreSQL (jdbc:tc:postgresql:17-alpine:///integrationTest).
  3. @Tag("integrationTest"): Permite filtrar a execução de testes de integração via Gradle ou pipelines de CI/CD.
  4. @AutoConfigureMockMvc: Adiciona o suporte ao mock do ambiente web do Spring MVC, permitindo disparar requisições HTTP simuladas de ponta a ponta sem a necessidade de subir um servidor de aplicação em uma porta TCP real.

No arquivo de build do projeto (build.gradle.kts), as dependências necessárias para suportar essa infraestrutura de testes de integração com a stack moderna (Gradle 9.8.2 Kotlin DSL, Spring Boot 4.1.2, Kotlin 2.4.21, JUnit Jupiter 6.0.3, AssertJ 3.27.7 e Testcontainers 2.0.5) são declaradas conforme o exemplo abaixo:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// build.gradle.kts
plugins {
    kotlin("jvm") version "2.4.21"
    kotlin("plugin.spring") version "2.4.21"
    id("org.springframework.boot") version "4.1.2"
    id("io.spring.dependency-management") version "1.1.7"
}

testing {
    suites {
        val test by getting(JvmTestSuite::class) {
            useJUnitJupiter("6.0.3")
            dependencies {
                implementation("org.springframework.boot:spring-boot-starter-test")
                implementation("org.springframework.boot:spring-boot-starter-web")
                implementation("org.assertj:assertj-core:3.27.7")
                implementation("org.testcontainers:testcontainers:2.0.5")
                implementation("org.testcontainers:postgresql:2.0.5")
                implementation("org.testcontainers:jdbc:2.0.5")
                implementation("org.postgresql:postgresql:42.7.5")
            }
        }
    }
}

Segundo, quando criamos uma nova classe de teste que contenha testes de integração para uma API REST, devemos seguir estes passos:

  1. Anotar a classe de teste com a anotação @WebIntegrationTest.
  2. Criar uma propriedade privada somente leitura (private val) que contenha um objeto MockMvc e injetar o valor dessa propriedade utilizando injeção via construtor (@Autowired private val mockMvc: MockMvc). Nosso objeto request builder utilizará essa propriedade quando enviar requisições HTTP ao sistema sob teste.
  3. Criar um novo objeto request builder (TaskCrudRequestBuilder) e armazenar esse objeto em uma propriedade privada somente leitura (private val requestBuilder = TaskCrudRequestBuilder(mockMvc)). Nossas funções de teste utilizarão essa propriedade quando enviarem requisições HTTP ao sistema sob teste.

Depois de configurarmos o sistema sob teste, o código-fonte da nossa classe de teste fica da seguinte forma:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
package com.cleantestautomation.tb2020.task

import com.cleantestautomation.tb2020.common.WebIntegrationTest
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.test.web.servlet.MockMvc

@WebIntegrationTest
class FindAllTasksTest(@Autowired private val mockMvc: MockMvc) {

    private val requestBuilder = TaskCrudRequestBuilder(mockMvc)
}

[!NOTE] Injeção de Dependências via Construtor no JUnit Jupiter com Kotlin:

Ao contrário do JUnit 4 (que exigia anotações @Autowired em campos públicos ou variáveis mutáveis com lateinit var), o JUnit Jupiter (JUnit 5 e JUnit 6) suporta resolução de parâmetros via construtor por meio do SpringExtension.

No Kotlin, declarar @Autowired private val mockMvc: MockMvc no construtor primário da classe de teste garante:

  • Imutabilidade real: A propriedade é uma referência imutável (val), prevenindo reatribuições acidentais.
  • Inicialização segura: O objeto MockMvc é injetado pelo Spring antes da instanciação de qualquer propriedade dependente, permitindo que private val requestBuilder = TaskCrudRequestBuilder(mockMvc) seja inicializado imediatamente no corpo da classe, sem risco de UninitializedPropertyAccessException.

O Padrão Request Builder com MockMvc

A classe TaskCrudRequestBuilder atua como um wrapper especializado sobre a instância de MockMvc. Ela encapsula a complexidade da montagem das requisições HTTP (caminhos de endpoint, cabeçalhos Content-Type, conversão de payloads JSON e execução com o MockMvc ou com a MockMvc Kotlin DSL).

Veja um exemplo de como a classe TaskCrudRequestBuilder pode ser estruturada:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package com.cleantestautomation.tb2020.task

import org.springframework.http.MediaType
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.ResultActions
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post

class TaskCrudRequestBuilder(private val mockMvc: MockMvc) {

    /**
     * Envia uma requisição HTTP GET para recuperar todas as tarefas.
     */
    fun findAll(): ResultActions = mockMvc.perform(
        get("/api/task")
            .accept(MediaType.APPLICATION_JSON)
    )

    /**
     * Envia uma requisição HTTP GET para recuperar uma tarefa pelo identificador.
     */
    fun findById(id: Long): ResultActions = mockMvc.perform(
        get("/api/task/{id}", id)
            .accept(MediaType.APPLICATION_JSON)
    )
}

Dessa forma, as funções de teste tornam-se extremamente legíveis, focadas no comportamento do negócio e desacopladas dos detalhes técnicos de baixo nível das chamadas HTTP.

Agora podemos configurar nossos testes de integração sem escrever nenhum código de configuração duplicado. Vamos resumir o que aprendemos nesta aula.

Resumo

Esta aula nos ensinou uma lição essencial:

  • Como queremos configurar nossos testes de integração sem escrever código de configuração duplicado em cada classe de teste, devemos criar uma anotação customizada (@WebIntegrationTest) que contenha a configuração compartilhada pelos nossos testes de integração de API REST (combinando @AutoConfigureMockMvc com @DatabaseIntegrationTest).

Obtenha o código-fonte no GitHub