Testes de Repositório com @DataJpaTest e Postgres Real
Depois de concluirmos este webinar, nós:
- Estaremos familiarizados com as anotações de Test Slices de persistência fornecidas pelo Spring Boot 4 (
@DataJpaTest,@JdbcTeste@JooqTest). - Saberemos escrever testes de integração para repositórios gerenciados pelo Spring com JUnit 6 e PostgreSQL real (ou banco em memória).
Este webinar assume que:
- Você está familiarizado com a inicialização de estado de banco de dados via
@Sqle Testcontainers.- Você sabe como configurar seus testes de integração.
- Você sabe escrever testes de integração para repositórios gerenciados pelo Spring.
- Você está familiarizado com o JUnit 6.
- Você sabe estruturar um projeto que executa testes unitários, de integração e End-to-End usando
@Tag.
A Configuração Comum Compartilhada Pelos Nossos Testes de Integração
Durante este webinar, escreveremos testes de integração para repositórios que utilizam Spring Data JPA, Spring JDBC (JdbcClient / JdbcTemplate) e jOOQ. Obviamente, cada classe de teste de integração utiliza uma configuração ligeiramente diferente. Contudo, todas as classes de teste de integração também compartilham uma configuração comum. Podemos criar essa configuração seguindo estes passos:
- Marcar a classe de teste como uma classe de teste de integração (
@IntegrationTest). - Configurar a conexão com o banco de dados PostgreSQL via
@Import(PostgresTestcontainersConfiguration::class)(ou manter o datasource configurado com@AutoConfigureTestDatabase(replace = Replace.NONE)). - Inicializar nosso banco de dados em um estado conhecido antes que um teste de integração seja executado usando
@Sql.
Após termos criado a configuração comum, o código-fonte base da nossa classe de teste de integração fica assim:
|
|
A anotação @IntegrationTest é uma meta-anotação customizada que nos permite marcar uma classe de teste como um teste de integração. Isso significa que todos os métodos de teste dessa classe são executados quando rodamos nossa suíte de integração. Esta anotação configura o perfil ativo do Spring e garante que todos os métodos de teste pertençam à tag correta do JUnit 6:
|
|
A seguir, descobriremos como podemos escrever testes de integração para um repositório que utiliza Spring Data JPA.
Escrevendo Testes de Integração para um Repositório Spring Data JPA
Nossa aplicação de exemplo possui um repositório Spring Data JPA chamado TaskRepository, e ele possui um método findAll() que retorna o id, status e title de todas as tarefas encontradas no banco de dados.
Podemos finalizar a configuração da nossa classe de teste para JPA seguindo estes passos:
- Anotar a classe de teste com
@DataJpaTest. No JUnit 6 e Spring Boot 4,@DataJpaTestjá inclui@ExtendWith(SpringExtension::class)como meta-anotação, garantindo que o Spring TestContext carregue apenas os componentes relevantes para testes JPA (@Entitye repositórios Spring Data JPA). - Injetar o repositório
TaskRepositoryvia construtor e escrever os três testes para o métodofindAll().
Após termos configurado nossos testes de integração, o código-fonte da nossa classe de teste fica assim:
|
|
Quando executamos nossos testes de integração, vemos que eles passam! A razão para isso é que a anotação @DataJpaTest configura o banco de dados de teste, escaneia as classes @Entity e configura os repositórios Spring Data JPA.
Há quatro pontos importantes que quero destacar antes de continuarmos este webinar:
- Se você quiser usar o datasource real configurado no seu arquivo de propriedades (ou fornecido por
@ServiceConnectiondo Testcontainers com PostgreSQL real) em vez de um banco de dados em memória, você deve anotar sua classe de teste com@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE).- A anotação
@DataJpaTestgarante que beans@Component,@Serviceou@Controllercomuns não sejam carregados noApplicationContext.- Por padrão, testes
@DataJpaTestsão transacionais e fazem rollback ao final de cada método de teste. Se você quiser desabilitar esse comportamento, anote sua classe de teste com@Transactional(propagation = Propagation.NOT_SUPPORTED).- Você também pode injetar os beans
TestEntityManagereJdbcTemplate(ouJdbcClient) diretamente na sua classe de teste.
Mesmo que nossos testes passem, o problema é que, se tivermos que escrever testes de integração para múltiplos repositórios Spring Data JPA, acabaremos escrevendo muita configuração duplicada. Podemos remover essa duplicação criando uma meta-anotação customizada que agrupa a configuração compartilhada pelos nossos testes JPA:
- Criar uma nova anotação
@CustomDataJpaTest(ou@PostgresDataJpaTest). - Mover as anotações
@DataJpaTest,@AutoConfigureTestDatabase(replace = Replace.NONE),@Import(PostgresTestcontainersConfiguration::class)e@IntegrationTestpara a anotação criada.
Após termos criado nossa anotação customizada, seu código-fonte fica assim:
|
|
Nosso último passo é remover a configuração duplicada da nossa classe de teste de integração e anotá-la com @CustomDataJpaTest:
|
|
Vamos avançar e descobrir como podemos escrever testes de integração para um repositório que utiliza JDBC.
Escrevendo Testes de Integração para um Repositório JDBC (@JdbcTest)
Nossa aplicação de exemplo possui um repositório que utiliza Spring JDBC (NamedParameterJdbcTemplate / JdbcClient). O nome desta classe de repositório é TaskJdbcRepository, e ela implementa um método findAll() que retorna o id, status e title de todas as tarefas encontradas no banco de dados.
Podemos configurar nosso teste de integração para o repositório JDBC seguindo estes passos:
- Anotar a classe de teste com
@JdbcTest, garantindo que o Spring TestContext carregue apenas a infraestrutura relevante para JDBC (DataSource,JdbcTemplate,NamedParameterJdbcTemplate,JdbcClient). - Garantir que o bean do repositório testado seja adicionado ao contexto de aplicação carregado usando
@Import(TaskJdbcRepository::class)(pois@JdbcTestnão escaneia classes@Repositoryautomaticamente).
Após termos configurado nossos testes de integração, o código-fonte da nossa classe de teste fica assim:
|
|
Há três pontos importantes sobre
@JdbcTest:
- Se você quiser usar o datasource real configurado nas propriedades (ou via Testcontainers), anote a classe com
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE).- A anotação
@JdbcTestgarante que componentes@Componentou@Repositorycomuns não sejam escaneados automaticamente — por isso importamos explicitamente@Import(TaskJdbcRepository::class).- Por padrão, testes
@JdbcTestsão transacionais e fazem rollback ao final de cada teste.
Novamente, podemos eliminar a configuração duplicada criando uma meta-anotação customizada @CustomJdbcTest:
|
|
E simplificamos a classe FindAllTasksWhenTwoTasksAreFoundJDBCTest anotando-a com @CustomJdbcTest e @Import(TaskJdbcRepository::class).
A seguir, descobriremos como podemos escrever testes de integração para um repositório que utiliza jOOQ.
Escrevendo Testes de Integração para um Repositório jOOQ (@JooqTest)
Nossa aplicação de exemplo possui um repositório que utiliza jOOQ chamado TaskJooqRepository, que também implementa o método findAll().
Podemos configurar nosso teste de integração para jOOQ seguindo estes passos:
- Anotar a classe de teste com
@JooqTest, que configura apenas oDataSourcee o beanDSLContextdo jOOQ. - Importar a classe do repositório testado com
@Import(TaskJooqRepository::class).
Após termos configurado nossos testes de integração, o código-fonte da nossa classe de teste fica assim:
|
|
Há três pontos importantes sobre
@JooqTest:
- Diferente de
@DataJpaTeste@JdbcTest, por padrão a anotação@JooqTestnão substitui o datasource por um banco em memória: ela utiliza o datasource configurado na sua aplicação (ou fornecido pelo Testcontainers), porque o código gerado pelo jOOQ é específico para o dialeto SQL do banco configurado (como PostgreSQL). Se você quiser substituí-lo por um banco em memória, precisará usar@AutoConfigureTestDatabase.- A anotação
@JooqTestgarante que beans@Componentcomuns não sejam carregados noApplicationContext.- Por padrão, testes
@JooqTesttambém são transacionais e fazem rollback ao final de cada método de teste.
E também podemos criar a meta-anotação @CustomJooqTest para evitar repetição nas nossas classes de teste jOOQ:
|
|
Prós e Contras das Anotações de Fatia de Persistência (Test Slices)
Essas três anotações possuem três vantagens:
- Se usarmos
@DataJpaTestou@JdbcTest, o Spring Boot autoconfigura toda a infraestrutura de persistência para nós sem precisarmos montar uma classe@Configurationmanual. - Se usarmos
@JooqTest(ou@AutoConfigureTestDatabase(replace = NONE)com Testcontainers), podemos rodar nossos testes de integração contra o mesmo banco de dados PostgreSQL usado em produção. - Como esses testes carregam apenas os componentes da camada de persistência, a inicialização individual do contexto é mais leve do que carregar a aplicação inteira. Contudo, lembre-se de que o Spring TestContext faz cache dos contextos carregados: se você usar muitos
@Import(...)diferentes em dezenas de classes@JdbcTest/@JooqTest, o Spring poderá criar vários pequenos contextos em vez de reutilizar um único contexto compartilhado.
Por outro lado, o comportamento padrão de @DataJpaTest e @JdbcTest de tentar substituir o DataSource por um banco em memória exige atenção: sempre adicione @AutoConfigureTestDatabase(replace = Replace.NONE) (ou use @ServiceConnection do Spring Boot 4 com Testcontainers, que já desativa a substituição por banco em memória automaticamente!) para garantir que seus testes rodem contra o PostgreSQL real.
Resumo
Este webinar nos ensinou cinco coisas:
- Por padrão,
@DataJpaTeste@JdbcTestconfiguram um banco em memória, a menos que usemos@AutoConfigureTestDatabase(replace = Replace.NONE)ou@ServiceConnectiondo Testcontainers. - Por padrão,
@JooqTestutiliza o datasource real configurado na aplicação. - Por padrão, os testes anotados com
@DataJpaTest,@JdbcTeste@JooqTestsão transacionais e fazem rollback ao final de cada método de teste. - Essas anotações garantem que beans
@Componente@Servicede outras camadas não sejam carregados noApplicationContext. - É uma excelente prática remover configurações duplicadas criando meta-anotações customizadas (
@CustomDataJpaTest,@CustomJdbcTest,@CustomJooqTest).