Integrando Inicialização de Dados com o TestContext

Ao concluirmos esta aula, nós:

  • Saberemos o que é o Spring Test DbUnit e como ele se integra aos TestExecutionListeners do Spring Test Framework.
  • Saberemos como declarar as dependências necessárias no Maven e no Gradle 9.
  • Compreenderemos como registrar e combinar TestExecutionListeners (ou usar o mergeMode = MergeMode.MERGE_WITH_DEFAULTS) para inicializar nosso banco de dados em testes de integração.

O Que É o Spring Test DbUnit (e os TestExecutionListeners do Spring)?

O ciclo de vida de um teste de integração no Spring Test Framework é orquestrado por componentes chamados TestExecutionListeners. Eles interceptam cada fase da execução do teste (antes da classe, na injeção de dependências da instância de teste, antes do método de teste transacional e após o método de teste).

O Spring Test DbUnit é a biblioteca que conecta o DbUnit ao ciclo de vida do Spring Test Framework através de um listener dedicado (DbUnitTestExecutionListener), oferecendo três anotações principais:

  1. @DatabaseSetup: Inicializa o banco de dados em um estado conhecido antes da execução do método de teste (de forma análoga à anotação nativa @Sql(executionPhase = ExecutionPhase.BEFORE_TEST_METHOD) do próprio Spring Test).
  2. @ExpectedDatabase: Verifica e escreve asserções sobre o conteúdo do banco de dados após a execução do método de teste.
  3. @DbUnitConfiguration: Permite customizar o carregador de datasets (DataSetLoader) e a conexão utilizada pelo DbUnit.

Vamos ver primeiro como obter as dependências necessárias no Maven e no Gradle.


Obtendo as Dependências Necessárias

Para integrarmos o DbUnit ao Spring Test Framework, declaramos as dependências dbunit e spring-test-dbunit no nosso script de build.

Se estivermos usando o Maven (pom.xml):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
<dependency>
    <groupId>org.dbunit</groupId>
    <artifactId>dbunit</artifactId>
    <version>2.8.0</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>com.github.springtestdbunit</groupId>
    <artifactId>spring-test-dbunit</artifactId>
    <version>1.3.0</version>
    <scope>test</scope>
</dependency>

Se estivermos usando o Gradle 9 (Kotlin DSL — build.gradle.kts), adicionamos na nossa suíte integrationTest (ou em testImplementation):

1
2
3
4
dependencies {
    integrationTestImplementation("org.dbunit:dbunit:2.8.0")
    integrationTestImplementation("com.github.springtestdbunit:spring-test-dbunit:1.3.0")
}

Nota sobre @Sql Nativo no Spring Boot 4: Se você optar por usar scripts SQL puros com a anotação @Sql do Spring Test (org.springframework.test.context.jdbc.Sql), você não precisa adicionar nenhuma dependência extra além do spring-boot-starter-test, pois o SqlScriptsTestExecutionListener já vem registrado por padrão no Spring TestContext Framework!

Vamos aprender agora como configurar os TestExecutionListeners na nossa classe de teste de integração.


Configurando os TestExecutionListeners no Spring Test

Normalmente não precisamos nos preocupar com TestExecutionListeners porque o Spring Test Framework registra automaticamente uma lista padrão de listeners (incluindo injeção de dependências, contexto de servlet, transações, scripts @Sql, etc.).

Porém, quando queremos utilizar o Spring Test DbUnit, precisamos registrar o listener adicional DbUnitTestExecutionListener usando a anotação @TestExecutionListeners.

Ao testarmos repositórios ou serviços que acessam o banco de dados, a configuração mínima de listeners que precisamos ter ativa inclui:

  • DependencyInjectionTestExecutionListener: Realiza a injeção de dependências (@Autowired) e inicialização das instâncias de teste.
  • TransactionalTestExecutionListener: Gerencia as transações de teste que respeitam a anotação @Transactional.
  • DbUnitTestExecutionListener (ou TransactionDbUnitTestExecutionListener): Processa as anotações @DatabaseSetup, @ExpectedDatabase e @DbUnitConfiguration.

Veja como fica a nossa classe de teste FindMessageByIdTest declarando explicitamente esses três listeners:

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

import com.github.springtestdbunit.DbUnitTestExecutionListener
import org.junit.jupiter.api.Tag
import org.junit.jupiter.api.extension.ExtendWith
import org.springframework.test.context.ContextConfiguration
import org.springframework.test.context.TestExecutionListeners
import org.springframework.test.context.junit.jupiter.SpringExtension
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener
import org.springframework.test.context.transaction.TransactionalTestExecutionListener

@ExtendWith(SpringExtension::class)
@ContextConfiguration(classes = [PersistenceContext::class])
@TestExecutionListeners(
    value = [
        DependencyInjectionTestExecutionListener::class,
        TransactionalTestExecutionListener::class,
        DbUnitTestExecutionListener::class
    ]
)
@Tag("integrationTest")
class FindMessageByIdTest {
}

Dica Moderna: MergeMode.MERGE_WITH_DEFAULTS! No Spring Test moderno, você não precisa listar manualmente todos os listeners padrão toda vez que quiser adicionar um novo listener! Basta usar o atributo mergeMode = TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS:

1
2
3
4
5
6
7
@SpringBootTest
@TestExecutionListeners(
    value = [DbUnitTestExecutionListener::class],
    mergeMode = TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS
)
@Tag("integrationTest")
class FindMessageByIdTest

Com MERGE_WITH_DEFAULTS, o Spring mantém todos os seus listeners nativos (injeção, transações, MockMvc, Spring Security, @Sql, MockBean) e simplesmente adiciona o DbUnitTestExecutionListener à lista!

(Nota sobre Transações: Caso você observe que dados inseridos pelo @DatabaseSetup não estão visíveis dentro da mesma transação do teste ou vice-versa, você pode substituir DbUnitTestExecutionListener::class por TransactionDbUnitTestExecutionListener::class).

Com o DbUnitTestExecutionListener habilitado na configuração padrão, o DbUnit:

  1. Obtém conexões com o banco de dados procurando um bean do Spring chamado dataSource ou dbUnitDatabaseConnection.
  2. Carrega os datasets XML utilizando a classe padrão FlatXmlDataSetLoader.

Esse é um ótimo começo, mas para podermos utilizar replacement datasets ([null], [empty_string]) e customizar propriedades do DbUnit ou conexões de container via Testcontainers, precisamos avançar para a próxima aula!


Resumo

Esta aula nos ensinou três coisas fundamentais:

  • O Spring Test DbUnit integra o DbUnit ao ciclo de vida do Spring Test Framework (@DatabaseSetup, @ExpectedDatabase, @DbUnitConfiguration), trabalhando lado a lado com o suporte nativo do Spring (@Sql).
  • Para usar o Spring Test DbUnit, declaramos as dependências dbunit e spring-test-dbunit no escopo de teste de integração.
  • Habilitamos o Spring Test DbUnit registrando o listener DbUnitTestExecutionListener com a anotação @TestExecutionListeners (preferencialmente usando mergeMode = MergeMode.MERGE_WITH_DEFAULTS).