Testes de Integração de Repositório: Consultas
Ao finalizarmos esta aula, seremos capazes de escrever testes de integração para funções de repositório que leem dados do banco de dados.
Esta aula assume que:
- Você está familiarizado com o DbUnit (ou gerenciamento de datasets de teste com
@DatabaseSetup).- Você está familiarizado com o JUnit Jupiter (JUnit 5 / JUnit 6.0.3).
- Você sabe configurar o sistema sob teste ao escrever testes de integração para repositórios Spring com Kotlin e JUnit Jupiter (conforme demonstrado na aula de configuração de testes de integração de repositório).
Obtenha o código-fonte no GitHub
Introdução ao Sistema Sob Teste
Durante esta aula, escreveremos testes de integração para a função findAll() da classe TaskRepository. Esta função não recebe parâmetros e retorna uma lista de objetos TaskListItemDTO. Mais especificamente, essa função retorna uma lista de tarefas encontradas no banco de dados e ordena a lista retornada em ordem crescente utilizando o ID da tarefa como critério de ordenação.
O código-fonte da classe TaskListItemDTO é o seguinte:
|
|
O código-fonte do enum TaskStatus é o seguinte:
|
|
A seguir, descobriremos como podemos escrever testes de integração para a nossa função de repositório.
Escrevendo Testes de Integração para Nossa Função de Repositório
Podemos escrever testes de integração para a função findAll() seguindo estes passos:
Primeiro, precisamos criar uma classe de teste que contenha nossas funções de teste de integração e anotar essa classe de teste com a anotação customizada @DatabaseIntegrationTest (que configuramos na aula anterior com @SpringBootTest, @ActiveProfiles("integrationTest") e o listener do DbUnit). Depois de criar a classe de teste, devemos declarar uma propriedade privada somente leitura (private val) que contenha o objeto TaskRepository e injetar o valor dessa propriedade via injeção por construtor utilizando @Autowired.
Após realizarmos esse passo, o código-fonte da nossa classe de teste fica da seguinte forma:
|
|
Nota sobre Injeção por Construtor em Testes Kotlin: No ecossistema moderno com Spring Boot 4.1.2 e JUnit Jupiter 6.0.3, a injeção via construtor na classe de teste é a abordagem recomendada e idiomática em Kotlin, pois permite manter o repositório como uma propriedade imutável (
val) e não anulável.
Segundo, devemos garantir que o sistema sob teste funcione conforme o esperado quando nenhuma tarefa for encontrada no banco de dados. Podemos escrever o teste de integração necessário seguindo estes passos:
- Criar uma classe interna (
inner class) anotada com@Nestedque agrupe nossos testes de integração para esse cenário. - Anotar a classe interna com a anotação
@DatabaseIntegrationTest. Isso é necessário porque, por padrão nas suítes de teste de integração de banco de dados, queremos garantir que toda a infraestrutura do Spring TestContext e os listeners do DbUnit sejam aplicados com precisão às classes aninhadas. - Inserir os dados de teste no banco de dados. Nossos datasets do DbUnit devem garantir que o banco de dados contenha usuários válidos, mas nenhuma tarefa e nenhuma tag associada.
- Escrever uma função de teste que verifique se a função
findAll()retorna uma lista vazia.
Depois de escrevermos os testes de integração necessários, o código-fonte da nossa classe de teste fica da seguinte forma:
|
|
O conjunto de dados do DbUnit que insere os dados de usuário em nosso banco de dados (users.xml) é o seguinte:
|
|
O conjunto de dados do DbUnit que garante que o banco de dados não possua tarefas nem tags (no-tasks-and-tags.xml) é o seguinte:
|
|
Terceiro, devemos escrever os testes de integração que garantam que o sistema sob teste funcione como o esperado quando duas tarefas forem encontradas no banco de dados. Podemos escrever esses testes seguindo estes passos:
- Criar uma classe interna (
inner class) anotada com@Nestedpara conter esses testes de integração. - Anotar a classe interna com a anotação
@DatabaseIntegrationTest. - Garantir que possamos injetar um objeto
SoftAssertionsna nossa função de teste via parâmetro. Utilizaremos asserções suaves (soft assertions) ao escrever as asserções para as propriedades dos objetos retornados, garantindo que todas as falhas sejam reportadas simultaneamente caso haja divergências. Para habilitar essa injeção de parâmetros no JUnit Jupiter, registramos a extensão@ExtendWith(SoftAssertionsExtension::class). - Inserir os dados de teste no banco de dados com
@DatabaseSetup. Nossos datasets do DbUnit devem carregar usuários e duas tarefas com suas respectivas tags. - Garantir que nossos datasets do DbUnit sejam carregados pela classe
ReplacementDataSetLoader. Essa classe intercepta e converte marcadores como[null]em literaisnullno banco de dados. Para configurá-la, anotamos a classe com@DbUnitConfiguration(dataSetLoader = ReplacementDataSetLoader::class). - Escrever uma função de teste que verifique se a função
findAll()retorna exatamente duas tarefas. - Escrever uma função de teste que verifique se a função
findAll()retorna a tarefa mais antiga como o primeiro elemento da lista retornada. - Escrever uma função de teste que verifique se a função
findAll()retorna a tarefa mais recente como o segundo elemento da lista retornada.
Depois de escrevermos nossos testes de integração, a parte relevante da nossa classe de teste fica da seguinte forma:
|
|
Leitura Adicional:
O dataset do DbUnit que insere duas tarefas no banco de dados (tasks.xml) fica da seguinte forma:
|
|
A classe Tasks é uma classe utilitária de constantes que centraliza os valores das colunas usados em nosso dataset do DbUnit, facilitando a manutenção e prevenindo magic numbers ou hardcoded strings espalhadas pelos testes. Seu código-fonte em Kotlin fica da seguinte forma:
|
|
Agora podemos escrever testes de integração para funções de repositório que leem dados do banco de dados. Vamos resumir o que aprendemos nesta aula.
Resumo
Esta aula nos ensinou dois conceitos essenciais:
- Como o framework Spring TestContext historicamente não repassa por padrão todas as configurações para classes de teste aninhadas (ou caso a herança não esteja ativada via anotações como
@NestedTestConfiguration), devemos anotar nossas classes aninhadas (inner classcom@Nested) explicitamente com a anotação@DatabaseIntegrationTest. - Se quisermos escrever asserções suaves usando objetos
SoftAssertionsinjetados diretamente como parâmetros em nossas funções de teste no JUnit Jupiter (JUnit 5 / JUnit 6.0.3), devemos utilizar a extensãoSoftAssertionsExtensiondo AssertJ 3.27.7 (@ExtendWith(SoftAssertionsExtension::class)).