Configurando o Sistema Sob Especificação com kotest-extensions-spring

Depois de concluirmos esta aula, nós:

  • Saberemos como adicionar a dependência kotest-extensions-spring com Maven e Gradle.
  • Compreenderemos como ativar a SpringExtension do Kotest para integrar o Spring TestContext Framework às nossas especificações.
  • Saberemos como configurar o sistema sob especificação tanto em aplicações Spring MVC tradicionais quanto em aplicações Spring Boot 4.1.2 com @SpringBootTest, @AutoConfigureMockMvc e Testcontainers.

1. Obtendo as Dependências Necessárias

No Spock Framework, precisávamos declarar a dependência spock-spring para integrar o Spring TestContext Framework às especificações.

No Kotest 6, esse papel é cumprido pelo módulo oficial kotest-extensions-spring (io.kotest:kotest-extensions-spring, alinhado pelo kotest-bom:6.2.6). Ele fornece a extensão SpringExtension, que conecta o ciclo de vida do TestContextManager do Spring aos callbacks da especificação do Kotest e permite injetar beans do Spring diretamente no construtor da especificação!

Com Maven

Adicionamos a dependência kotest-extensions-spring-jvm no pom.xml:

1
2
3
4
5
6
<dependency>
    <groupId>io.kotest</groupId>
    <artifactId>kotest-extensions-spring-jvm</artifactId>
    <version>6.2.6</version>
    <scope>test</scope>
</dependency>

Com Gradle 9.8.2 (Kotlin DSL)

Adicionamos io.kotest:kotest-extensions-spring na configuração da nossa suíte integrationTest:

1
2
3
4
5
6
dependencies {
    implementation(platform("io.kotest:kotest-bom:6.2.6"))
    implementation("io.kotest:kotest-runner-junit5")
    implementation("io.kotest:kotest-assertions-core")
    implementation("io.kotest:kotest-extensions-spring")
}

[!IMPORTANT] Como ativar a SpringExtension no Kotest 6

Você pode ativar a SpringExtension de duas maneiras:

  1. Por especificação: adicionando extensions(SpringExtension) dentro do corpo da especificação (ou anotando a classe).
  2. Globalmente para todo o projeto (Recomendado!): registrando SpringExtension na classe ProjectConfig : AbstractProjectConfig() do seu projeto:
1
2
3
4
5
6
7
8
package io.kotest.provided

import io.kotest.core.config.AbstractProjectConfig
import io.kotest.extensions.spring.SpringExtension

class ProjectConfig : AbstractProjectConfig() {
    override fun extensions() = listOf(SpringExtension)
}

Com isso, qualquer especificação anotada com @ContextConfiguration, @SpringBootTest ou @DataJpaTest terá injeção de dependência e gerenciamento de transações do Spring habilitados automaticamente!

Vamos ver agora como configurar o sistema sob especificação tanto em uma aplicação Spring MVC tradicional quanto em uma aplicação Spring Boot 4!


2. Configurando Testes de Integração para Aplicações Spring Framework Tradicionais

Quando utilizamos o Spring Framework clássico (sem auto-configuração do Spring Boot), configuramos nossa classe de especificação de integração seguindo cinco passos:

  1. Configuramos a classe de configuração do contexto (@ContextConfiguration(classes = [IntegrationTestContext::class])).
  2. Se o ApplicationContext carregado for um WebApplicationContext, anotamos a especificação com @WebAppConfiguration.
  3. Garantimos que os listeners padrão do Spring Test (DependencyInjectionTestExecutionListener, TransactionalTestExecutionListener, SqlScriptsTestExecutionListener, WithSecurityContextTestExecutionListener) estejam ativos.
  4. Garantimos que os testes de integração rodem com o perfil Spring "integrationTest" (@ActiveProfiles(Profiles.INTEGRATION_TEST)).
  5. Registramos extensions(SpringExtension) na especificação:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
package com.testwithspring.master.web

import com.testwithspring.master.IntegrationTest
import com.testwithspring.master.config.Profiles
import io.kotest.core.annotation.Tags
import io.kotest.core.spec.style.BehaviorSpec
import io.kotest.extensions.spring.SpringExtension
import org.springframework.test.context.ActiveProfiles
import org.springframework.test.context.ContextConfiguration
import org.springframework.test.context.web.WebAppConfiguration

@ContextConfiguration(classes = [IntegrationTestContext::class])
@WebAppConfiguration
@Tags("IntegrationTest")
@ActiveProfiles(Profiles.INTEGRATION_TEST)
class ExampleSpringSpec : BehaviorSpec({

    extensions(SpringExtension)

})

Se estivermos testando um repositório ou serviço, basta receber o bean no construtor da especificação (class ExampleSpringSpec(private val repository: TaskRepository) : BehaviorSpec(...)). E se estivermos testando um controlador Spring MVC em uma aplicação Spring tradicional (sem Spring Boot), injetamos o WebApplicationContext e construímos o MockMvc com springSecurity() no callback beforeEach (ou diretamente como propriedade):

 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
27
28
29
30
31
32
33
package com.testwithspring.master.web

import com.testwithspring.master.IntegrationTest
import com.testwithspring.master.config.Profiles
import io.kotest.core.annotation.Tags
import io.kotest.core.spec.style.BehaviorSpec
import io.kotest.extensions.spring.SpringExtension
import org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity
import org.springframework.test.context.ActiveProfiles
import org.springframework.test.context.ContextConfiguration
import org.springframework.test.context.web.WebAppConfiguration
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.setup.MockMvcBuilders
import org.springframework.web.context.WebApplicationContext

@ContextConfiguration(classes = [IntegrationTestContext::class])
@WebAppConfiguration
@Tags("IntegrationTest")
@ActiveProfiles(Profiles.INTEGRATION_TEST)
class ExampleSpringMvcSpec(
    private val webAppContext: WebApplicationContext
) : BehaviorSpec({

    extensions(SpringExtension)

    lateinit var mockMvc: MockMvc

    beforeEach {
        mockMvc = MockMvcBuilders.webAppContextSetup(webAppContext)
            .apply<org.springframework.test.web.servlet.setup.DefaultMockMvcBuilder>(springSecurity())
            .build()
    }
})

3. Configurando Testes de Integração para Aplicações Spring Boot 4

Quando utilizamos o Spring Boot 4.1.2, tiramos proveito dos recursos de auto-configuração do Spring Boot Test para eliminar todo o código repetitivo acima!

Fazemos isso em três passos:

  1. Substituímos @ContextConfiguration e @WebAppConfiguration pela anotação @SpringBootTest(classes = [IntegrationTestContext::class]).
  2. Se precisarmos do MockMvc (com os filtros do Spring Security já integrados automaticamente!), basta adicionar a anotação @AutoConfigureMockMvc na classe de especificação.
  3. Injetamos o MockMvc diretamente no construtor da classe de especificação do Kotest!

Veja como a nossa classe de especificação fica limpa e concisa com Spring Boot 4 e Kotest 6:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package com.testwithspring.master.web

import com.testwithspring.master.IntegrationTest
import com.testwithspring.master.config.Profiles
import io.kotest.core.annotation.Tags
import io.kotest.core.spec.style.BehaviorSpec
import io.kotest.extensions.spring.SpringExtension
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.test.context.ActiveProfiles
import org.springframework.test.web.servlet.MockMvc

@SpringBootTest(classes = [IntegrationTestContext::class])
@AutoConfigureMockMvc
@Tags("IntegrationTest")
@ActiveProfiles(Profiles.INTEGRATION_TEST)
class ExampleSpringBootSpec(
    private val mockMvc: MockMvc
) : BehaviorSpec({

    extensions(SpringExtension)

    // Nossos cenários Given / When / Then usam mockMvc diretamente!
})

Resumo

Nesta aula, aprendemos três pontos fundamentais:

  • O módulo io.kotest:kotest-extensions-spring integra o Spring TestContext Framework ao Kotest 6 por meio da SpringExtension.
  • A versão de kotest-extensions-spring deve estar alinhada com kotest-runner-junit5 (o que é garantido automaticamente ao usar o io.kotest:kotest-bom:6.2.6).
  • Em aplicações Spring Boot 4, combinamos @SpringBootTest, @AutoConfigureMockMvc e injeção no construtor da especificação para ter o MockMvc e o Spring Security prontos para uso sem nenhum código boilerplate.