Testando Tipos Customizados no PostgreSQL: Fundamentos
Ao finalizarmos esta aula, seremos capazes de:
- Saber como implementar um tipo de dado customizado no DbUnit.
- Compreender como garantir que o DbUnit utilize nosso tipo de dado customizado em testes de integração.
Esta aula assume que:
- Você já está familiarizado com o DbUnit (gerenciamento e carregamento de datasets de teste via
@DatabaseSetup).- Você sabe configurar testes de integração que utilizam o ecossistema Spring Boot com Kotlin 2.4.21, Spring Boot 4.1.2, Gradle 9.8.2 e JUnit Jupiter 6.0.3.
- Você sabe como executar bancos de dados PostgreSQL em contêineres de teste utilizando Testcontainers 2.0.5.
Obter o código-fonte no GitHub
Implementando um Tipo de Dado Customizado no DbUnit
Quando queremos implementar um tipo de dado customizado no DbUnit, precisamos criar uma nova classe de tipo de dados que estende a classe AbstractDataType.
Depois de criarmos essa classe, seu esqueleto inicial de código-fonte fica da seguinte forma:
|
|
Podemos agora implementar nosso tipo de dado customizado no DbUnit seguindo estes passos:
Primeiro, precisamos implementar um construtor que invoque o construtor da classe AbstractDataType. Ao invocar o construtor da superclasse AbstractDataType, devemos fornecer quatro argumentos:
- O nome do tipo de dado SQL (
name: String) suportado pelo nosso tipo de dados do DbUnit. - O código numérico do tipo de dado SQL (
sqlType: Int) suportado pelo nosso tipo de dados do DbUnit (como as constantes padronizadas emjava.sql.Types). - A classe do tipo de objeto (
classType: Class<*>) que contém o valor inserido na coluna da tabela do banco de dados. - Um valor booleano (
isNumber: Boolean) que especifica se nosso tipo de dados do DbUnit suporta valores numéricos.
Depois de implementarmos o construtor obrigatório, o código-fonte da nossa classe de tipo de dados em Kotlin 2.4.21 fica da seguinte forma:
|
|
Para fins de referência e comparação com o ecossistema Java clássico, a implementação equivalente em Java seria:
|
|
Segundo, precisamos implementar o método typeCast() da classe DataType (definido na interface DataType e herdado via AbstractDataType). Esse método transforma o objeto fornecido como parâmetro no método em um objeto que será inserido em uma coluna de uma tabela do banco de dados.
Depois de implementarmos esse método, o código-fonte da nossa classe de tipo de dados fica da seguinte forma:
|
|
Nota:
Este tipo de dados é inútil neste momento porque não faz nada (retorna apenas
null). Se você quiser saber como implementar tipos de dados customizados reais e funcionais no DbUnit (como suporte a colunas de arrayTEXT[]ou colunasJSONBno PostgreSQL), confira as próximas aulas deste capítulo.
A seguir, descobriremos como podemos garantir que o DbUnit utilize nosso tipo de dado customizado.
Alterando a Configuração do DbUnit
Quando queremos garantir que o DbUnit utilize nosso tipo de dado customizado, precisamos seguir estes passos:
Primeiro, precisamos estender a classe factory de tipos de dados (data type factory) que assegura que o DbUnit consiga manipular os tipos de dados do banco de dados utilizado. Como nossa aplicação utiliza o banco de dados PostgreSQL (gerenciado em tempo de teste via Testcontainers 2.0.5), devemos estender a classe PostgresqlDataTypeFactory.
Depois de criarmos nossa classe factory de tipos de dados, devemos sobrescrever o método createDataType() da interface IDataTypeFactory e implementá-lo seguindo estes passos:
- Se o
sqlType(ou o nomesqlTypeName) for igual ao tipo de dados suportado pelo nosso tipo customizado do DbUnit, retornamos uma nova instância deCustomDbUnitDataType. - Caso contrário, invocamos o método
createDataType()da superclasse (super.createDataType(...)). Isso garante que o DbUnit continue manipulando normalmente todos os tipos de dados padrão (default) do banco de dados utilizado.
O código-fonte da nossa classe factory de tipos de dados em Kotlin 2.4.21 fica da seguinte forma:
|
|
Segundo, precisamos criar uma nova classe de configuração do Spring e anotá-la com a anotação @Configuration. Depois de criarmos essa classe, precisamos implementá-la seguindo estes passos:
- Criar um bean
DatabaseConfigBeanque garante que o DbUnit utilize nossa factory customizada de tipos de dados (CustomPostgreSQLDataTypeFactory). - Assegurar que o DbUnit utilize a configuração customizada fornecida pelo bean
DatabaseConfigBean, registrando um bean do tipoDatabaseDataSourceConnectionFactoryBean.
Depois de escrevermos nossa classe de configuração, seu código-fonte em Kotlin 2.4.21 com Spring Boot 4.1.2 fica da seguinte forma:
|
|
Entendendo como esses dois beans colaboram com o spring-test-dbunit:
DatabaseConfigBean: funciona como uma ponte declarativa para o objetoDatabaseConfigdo DbUnit. Ao chamarconfig.datatypeFactory = CustomPostgreSQLDataTypeFactory(), definimos a propriedade internaPROPERTY_DATATYPE_FACTORYdo DbUnit, instruindo-o a consultar nossa factory ao interpretar colunas do PostgreSQL.DatabaseDataSourceConnectionFactoryBean: a integração dospring-test-dbunitprocura um bean que forneça instâncias deIDatabaseConnection. Esse bean factory recebe oDataSourceautoconfigurado pelo Spring Boot (que aponta para o banco de dados PostgreSQL do Testcontainers) e injeta a nossa instância deDatabaseConfigBean.
Antes de podermos utilizar nosso tipo de dado customizado do DbUnit em nossos datasets de teste, precisamos garantir que nossos testes de integração carreguem essa nossa classe de configuração customizada. Vamos descobrir como fazer isso.
Configurando Nossos Testes de Integração
Quando usamos a anotação @SpringBootTest, precisamos garantir que nossos testes de integração carreguem o contexto de aplicação (ApplicationContext) da nossa aplicação Spring Boot e adicionem nossos beans customizados do Spring ao contexto carregado.
Para evitar repetição de código em cada classe de teste, criamos uma anotação customizada de teste (meta-anotação composta) denominada @DatabaseIntegrationTest.
O código-fonte da anotação que configura nossos testes de integração em Kotlin 2.4.21 e JUnit Jupiter 6.0.3 fica da seguinte forma:
|
|
Analisando a estrutura da nossa anotação:
@SpringBootTest(classes = [CustomDbUnitDataTypeApplication::class, TestDatabaseConfiguration::class]): instrui o Spring Boot a carregar a classe principal da aplicação e a registrar explicitamente nossa classeTestDatabaseConfiguration. Dessa forma, os beansdbUnitDatabaseConfigedbUnitDatabaseConnectiontornam-se ativos no contexto de teste.@TestExecutionListeners: registra o listenerDbUnitTestExecutionListenerdospring-test-dbunitcom o modo de mesclagemMERGE_WITH_DEFAULTS. Esse listener é responsável por interceptar o ciclo de vida dos testes do JUnit Jupiter e acionar os mecanismos de carga do DbUnit (como@DatabaseSetup,@ExpectedDatabasee@DatabaseTearDown).@ActiveProfiles("integrationTest"): ativa o perfil de testes de integração, que carrega as propriedades de banco de dados do Testcontainers (spring.datasource.url=jdbc:tc:postgresql:17-alpine:///integrationTest).@Tag("integrationTest"): categoriza o teste no JUnit Jupiter 6.0.3, possibilitando que a suíte seja executada de maneira filtrada ou dedicada no Gradle 9.8.2 (./gradlew test -PincludeTags=integrationTest).
Com essa meta-anotação configurada, qualquer classe de teste de integração que precise interagir com o PostgreSQL e carregar datasets com suporte a tipos customizados necessita apenas ser anotada com @DatabaseIntegrationTest:
|
|
Agora somos capazes de implementar um tipo de dado customizado no DbUnit e garantir que o DbUnit utilize nosso tipo customizado durante a execução dos testes. Vamos resumir o que aprendemos nesta aula.
Resumo
Esta aula nos ensinou quatro pontos fundamentais:
- Quando queremos implementar um tipo de dado customizado no DbUnit, precisamos estender a classe
AbstractDataTypee implementar o métodotypeCast(). - Uma factory de tipos de dados específica do banco de dados (como
PostgresqlDataTypeFactory) garante que o DbUnit consiga manipular os tipos de dados nativos do banco de dados utilizado. - Se quisermos adicionar suporte a novos tipos de dados no DbUnit, precisamos estender a classe factory de tipos de dados correta e sobrescrever o método
createDataType(). - Quando queremos utilizar nosso tipo de dado customizado em nossos datasets do DbUnit, devemos garantir que o DbUnit utilize nossa factory de tipos de dados configurando os beans
DatabaseConfigBeaneDatabaseDataSourceConnectionFactoryBeanno contexto do Spring Boot.