Integrando Testcontainers 2.0 com JUnit 6
Ao concluirmos este webinar, nós:
- Compreenderemos como podemos integrar o Testcontainers 2.0.5 com o JUnit Jupiter 6.0.3.
- Saberemos como criar uma extensão customizada do JUnit 6 que integra o Testcontainers ao ciclo de vida do JUnit Jupiter.
- Conseguiremos utilizar nossa extensão customizada (e também o suporte nativo
@Testcontainers/@Container) ao escrevermos testes com JUnit 6 e Kotlin 2.4.21.
Este webinar assume que:
Obtenha o código-fonte no GitHub
Utilizando a Abordagem Manual
Como nos lembramos da aula anterior, quando utilizávamos o Testcontainers com o antigo JUnit 4, podíamos iniciar e parar nossos contêineres Docker por meio de Rules do JUnit 4 (@ClassRule / @Rule). Quando utilizamos o Testcontainers com o JUnit Jupiter 6, podemos iniciar e parar nossos contêineres Docker manualmente utilizando métodos de setup (@BeforeAll) e teardown (@AfterAll).
Nota sobre o Testcontainers 2.0.5: O Testcontainers 2.0 conta com suporte integrado oficial para o JUnit Jupiter por meio do módulo
org.testcontainers:testcontainers-junit-jupiter:2.0.5(anotações@Testcontainerse@Container). Ainda assim, entender o controle manual e construir nossa própria extensão JUnit 6 passo a passo revela exatamente como o modelo de extensões do JUnit Jupiter opera sob o capô.
Vamos escrever uma classe de teste que utiliza um contêiner Docker executando um banco de dados PostgreSQL. Podemos escrever essa classe de teste seguindo estes passos:
Primeiro, precisamos adicionar uma propriedade private e static (dentro do companion object com @JvmStatic em Kotlin) do tipo PostgreSQLContainer à nossa classe de teste e garantir que essa propriedade contenha uma referência para uma nova instância de PostgreSQLContainer.
Após adicionarmos essa propriedade à nossa classe de teste, seu código-fonte fica da seguinte forma:
|
|
Segundo, precisamos adicionar um método de configuração (setup) à nossa classe de teste e garantir que esse método seja executado antes que qualquer método de teste seja rodado (@BeforeAll). Após adicionarmos esse método à nossa classe de teste, devemos implementá-lo iniciando o nosso contêiner Docker.
Depois de escrevermos esse método de setup, o código-fonte da nossa classe de teste fica assim:
|
|
Terceiro, precisamos escrever o método de teste que garante que o banco de dados PostgreSQL foi iniciado com sucesso. Após escrevermos esse método de teste, o código-fonte da nossa classe de teste fica da seguinte forma:
|
|
Quarto, precisamos adicionar um método de finalização (teardown) à nossa classe de teste e garantir que esse método seja executado após todos os métodos de teste terem sido executados (@AfterAll). Depois de adicionarmos esse método à nossa classe de teste, devemos implementá-lo parando o nosso contêiner Docker.
Após escrevermos esse método de teardown, o código-fonte da nossa classe de teste fica assim:
|
|
Quando executamos o nosso teste, vemos que ele passa com sucesso.
Mesmo que este exemplo utilize um contêiner Docker que inicia um banco de dados PostgreSQL, o processo é exatamente o mesmo para todos os tipos de contêineres suportados pelo Testcontainers.
Leitura Adicional:
Agora já conseguimos iniciar e parar nosso contêiner Docker utilizando a abordagem manual. A desvantagem dessa abordagem é que precisamos adicionar código duplicado de ciclo de vida (@BeforeAll e @AfterAll) em nossas classes de teste. A seguir, descobriremos como podemos eliminar esse código repetitivo escrevendo uma extensão customizada do JUnit 6.
Escrevendo uma Extensão Customizada do JUnit 6
Se quisermos nos livrar do código duplicado, podemos escrever uma extensão customizada do JUnit Jupiter 6 que inicia e para o nosso contêiner Docker automaticamente. Os requisitos da nossa extensão customizada são:
- Ela deve obter o objeto do contêiner Docker a partir de um campo
privateestaticencontrado na classe de teste que utiliza nossa extensão customizada. - Ela deve iniciar o nosso contêiner Docker antes que qualquer método de teste seja executado.
- Ela deve parar o nosso contêiner Docker depois que todos os métodos de teste tiverem sido executados.
Podemos escrever essa extensão seguindo estes passos:
Primeiro, devemos criar uma nova classe que implementa as interfaces BeforeAllCallback e AfterAllCallback do JUnit Jupiter.
O código-fonte da classe criada fica da seguinte forma:
|
|
Segundo, precisamos escrever um método private fun getContainer() que retorna uma referência para o objeto GenericContainer<*> declarado na classe de teste. Esse método recebe um objeto Class<*> como parâmetro e localiza o objeto GenericContainer<*> necessário utilizando reflexão (Reflection).
Após implementarmos esse método e o método auxiliar isContainerField(), o código-fonte da nossa classe de extensão fica assim:
|
|
Terceiro, precisamos iniciar nosso contêiner Docker antes que qualquer método de teste seja executado (beforeAll) e pará-lo depois que todos os métodos de teste tiverem sido executados (afterAll). Após fazermos isso, o código-fonte completo da nossa classe de extensão fica da seguinte forma:
|
|
Agora escrevemos uma extensão customizada do JUnit 6 capaz de iniciar e parar nosso contêiner Docker automaticamente. Vamos avançar e descobrir como podemos utilizar nossa nova extensão customizada.
Utilizando Nossa Extensão Customizada
Quando quisermos escrever uma classe de teste que utiliza um contêiner Docker executando um banco de dados PostgreSQL gerenciado pela nossa extensão, devemos seguir estes passos:
Primeiro, precisamos registrar nossa extensão customizada. Podemos fazer isso anotando nossa classe de teste com a anotação @ExtendWith.
Após registrarmos nossa extensão customizada, o código-fonte da nossa classe de teste fica da seguinte forma:
|
|
Segundo, precisamos adicionar uma propriedade private e static (@JvmStatic no companion object) do tipo PostgreSQLContainer à nossa classe de teste e garantir que essa propriedade contenha uma referência para um novo objeto PostgreSQLContainer.
Após adicionarmos essa propriedade à nossa classe de teste, seu código-fonte fica assim:
|
|
Terceiro, precisamos escrever o método de teste que garante que o banco de dados PostgreSQL foi iniciado com sucesso. Após escrevermos esse método de teste, o código-fonte da nossa classe de teste fica da seguinte forma:
|
|
Quando executamos o nosso teste, vemos que ele passa.
Novamente, mesmo que este exemplo utilize um contêiner Docker que inicia um banco de dados PostgreSQL, o processo é exatamente o mesmo para todos os tipos de contêineres suportados.
Dica na Stack Moderna (Testcontainers 2.0.5): Na prática cotidiana com Testcontainers 2.0.5 e JUnit Jupiter 6.0.3, a extensão oficial
@Testcontainerse a anotação@Container(do artefatoorg.testcontainers:testcontainers-junit-jupiter:2.0.5) implementam exatamente esse mecanismo deBeforeAllCallback/AfterAllCallback(além deBeforeEachCallback/AfterEachCallbackpara campos de instância) que acabamos de construir!
Já sabemos como iniciar e parar contêineres Docker com o JUnit 6. Vamos resumir o que aprendemos neste webinar.
Resumo
Este webinar nos ensinou duas coisas fundamentais:
- Podemos iniciar e parar nosso contêiner Docker manualmente utilizando métodos de configuração (
@BeforeAll) e finalização (@AfterAll). - Se quisermos remover código duplicado das nossas classes de teste, podemos implementar uma extensão customizada do JUnit 6 (
BeforeAllCallbackeAfterAllCallback) — ou utilizar a extensão oficial@Testcontainersdo Testcontainers 2.0 — que inicia e para nosso contêiner Docker automaticamente.