Introdução ao Testcontainers 2.0
Ao final desta aula, seremos capazes de:
- Entender o que é o Testcontainers.
- Obter as dependências necessárias com o Gradle (Kotlin DSL).
- Compreender como utilizar contêineres genéricos (
GenericContainer). - Executar um banco de dados PostgreSQL com o Testcontainers (
PostgreSQLContainer). - Saber como executar um contêiner do Selenium WebDriver (
BrowserWebDriverContainer).
Obtenha o código-fonte no GitHub
O Que É o Testcontainers?
O Testcontainers nos permite iniciar um contêiner Docker (ou runtime OCI compatível) antes que nossos métodos de teste sejam executados e encerrá-lo após a conclusão dos testes. Na stack moderna do Spring Boot 4.1.2, ele requer um ambiente compatível com a API do Docker (Docker Engine, Docker Desktop, OrbStack, Rancher Desktop ou Podman), Java 21+ / Kotlin 2.4.21 e JUnit Jupiter 6.0.3 (no curso original de 2018, exigia Docker ou docker-machine, Java 8 e JUnit 4). É possível utilizar o Testcontainers no Linux, macOS e Windows com suporte de primeira classe em todos os sistemas operacionais modernos.
A biblioteca Testcontainers possui cinco modos principais de uso:
- Podemos executar um contêiner genérico (
GenericContainer). - Podemos executar um contêiner Docker que inicia um banco de dados relacional ou NoSQL. O Testcontainers suporta dezenas de bancos de dados dedicados, incluindo PostgreSQL, MySQL, MariaDB, Oracle XE/Free, MongoDB, Redis e muitos outros.
- Podemos iniciar um contêiner Docker que executa um navegador web. Após iniciar esse contêiner, podemos controlar o navegador web utilizando um
RemoteWebDriverdo Selenium. - Podemos executar contêineres que iniciam os serviços definidos em um arquivo Docker Compose (
ComposeContainer). - Podemos executar um contêiner Docker criado dinamicamente a partir de um
Dockerfile(ImageFromDockerfile).
[!NOTE] Esta aula concentra-se nos três primeiros modos de uso. Se você quiser obter mais informações sobre os modos de uso avançados, consulte a Documentação Oficial do Testcontainers para Java/Kotlin.
A seguir, descobriremos como obter as dependências necessárias com o Gradle.
Obtendo as Dependências Necessárias com o Gradle
Podemos obter as dependências necessárias no Gradle 9.8.2 (Kotlin DSL) adicionando os seguintes módulos ao nosso arquivo build.gradle.kts (gerenciados pela BOM do Spring Boot 4.1.2 ou pela BOM do Testcontainers 2.0.5):
- O módulo
testcontainerscontém a funcionalidade central (core) e fornece suporte para contêineres genéricos e Docker Compose. - O módulo
junit-jupiterfornece a integração declarativa com o ciclo de vida do JUnit Jupiter 6 (@Testcontainerse@Container). - O módulo
postgresqlfornece suporte especializado para a execução do banco de dados PostgreSQL. - O módulo
seleniumfornece suporte para a execução de contêineres Selenium WebDriver.
Em outras palavras, podemos obter as dependências necessárias adicionando o seguinte trecho à seção dependencies do nosso arquivo build.gradle.kts:
|
|
[!NOTE] Leitura Adicional:
Vamos avançar e descobrir como podemos configurar o ciclo de vida do contêiner Docker iniciado.
Configurando o Contêiner Docker Iniciado
Na stack moderna com JUnit Jupiter 6.0.3 e Testcontainers 2.0.5, configuramos o ciclo de vida do contêiner Docker anotando nossa classe de teste com @Testcontainers e utilizando a anotação @Container (que substitui as antigas regras @ClassRule e @Rule do JUnit 4 usadas na versão original do curso). Quando configuramos o contêiner Docker iniciado, temos duas opções principais:
- Se quisermos que o contêiner Docker seja iniciado apenas uma vez antes da execução de qualquer método de teste da classe (equivalente ao antigo
@ClassRuledo JUnit 4), devemos declarar uma propriedade dentro docompanion objectda nossa classe de teste (com@JvmStatic) e anotá-la com@Container. - Se quisermos que um novo contêiner Docker seja iniciado antes de cada método de teste e destruído logo após (equivalente ao antigo
@Ruledo JUnit 4), devemos declarar uma propriedade de instância na nossa classe de teste e anotá-la com@Container.
[!NOTE] Na maioria das vezes, devemos iniciar nossos contêineres Docker apenas uma vez por classe (ou de forma compartilhada na suíte), pois iniciá-los antes de cada método de teste pode tornar a execução da nossa suíte de testes excessivamente lenta. É por isso que todos os exemplos desta aula iniciam o contêiner Docker apenas uma vez no
companion object.
A seguir, veremos como podemos executar contêineres genéricos.
Executando Contêineres Genéricos
Se quisermos executar um contêiner genérico, devemos utilizar a classe GenericContainer. Ao criar um novo objeto GenericContainer, passamos o nome da imagem Docker (DockerImageName) como argumento do construtor. Depois de criar o objeto GenericContainer, podemos configurar o contêiner Docker iniciado utilizando os métodos fluentes da classe GenericContainer.
Vamos escrever um método de teste que utiliza um contêiner Docker executando uma imagem mínima baseada no Alpine Linux. Esse contêiner Docker expõe a porta 80. Quando enviamos uma requisição HTTP para a porta exposta, o contêiner Docker iniciado retorna o código de status HTTP 200 e a mensagem: 'TestContainers Rules!'.
Podemos escrever esse método de teste seguindo estes passos:
Primeiro, devemos configurar a instância de GenericContainer que inicia o nosso contêiner Docker. Ao configurar esse contêiner, precisamos definir a porta exposta (withExposedPorts) e o comando (withCommand) que retorna a resposta HTTP contendo a mensagem esperada.
Após configurarmos o GenericContainer, o código-fonte da nossa classe de teste fica da seguinte forma:
|
|
Segundo, devemos escrever um método de teste que garanta que o nosso contêiner Docker retorne a mensagem correta quando enviamos uma requisição GET para a porta exposta (utilizando o HttpClient nativo do Java 21+ ou cliente HTTP equivalente).
Após escrevermos o nosso método de teste, o código-fonte completo da nossa classe de teste fica assim:
|
|
[!NOTE] Quero destacar dois pontos importantes antes de continuarmos esta aula:
- Se quisermos obter o endereço de host/IP do contêiner Docker iniciado, devemos acessar a propriedade
host(métodogetHost(), que substituiu o antigogetContainerIpAddress()depreciado nas versões antigas do Testcontainers) da classeGenericContainer.- Se quisermos obter a porta real no host que nos permite conectar à porta exposta pelo contêiner, devemos invocar o método
getMappedPort()da classeGenericContainer. Ao invocar esse método, devemos passar o número da porta original exposta pelo contêiner como parâmetro.Leitura Adicional:
Vamos avançar e descobrir como podemos executar bancos de dados PostgreSQL com o Testcontainers.
Executando um Banco de Dados PostgreSQL
Se quisermos executar um contêiner que inicia um banco de dados PostgreSQL, devemos utilizar a classe PostgreSQLContainer. A classe PostgreSQLContainer permite especificar a imagem Docker do PostgreSQL no construtor:
- No Testcontainers clássico (1.8.0), o construtor sem argumentos iniciava o PostgreSQL
9.6.8. - No Testcontainers 2.0.5, especificamos explicitamente a versão desejada do banco de dados PostgreSQL passando o nome da imagem Docker (por exemplo,
"postgres:16-alpine"ouDockerImageName.parse("postgres:16-alpine")) como argumento do construtor.
Depois de criarmos um novo objeto PostgreSQLContainer, podemos configurar o banco de dados iniciado utilizando os métodos da classe PostgreSQLContainer.
Vamos escrever um método de teste que inicia um contêiner PostgreSQL e valida a conexão JDBC. Podemos escrever esse método de teste seguindo estes passos:
Primeiro, devemos configurar a instância de PostgreSQLContainer que inicia o nosso banco de dados PostgreSQL. Após configurarmos o contêiner, o código-fonte da nossa classe de teste fica da seguinte forma:
|
|
Segundo, devemos escrever o método de teste que garante que o banco de dados PostgreSQL foi iniciado com sucesso executando a consulta SELECT 42. Após escrevermos esse método de teste, o código-fonte da nossa classe de teste fica da seguinte forma:
|
|
[!NOTE] Como podemos observar, podemos obter todos os detalhes de conexão do banco de dados PostgreSQL iniciado (
jdbcUrl,usernameepassword) utilizando as propriedades/métodos getters fornecidos pela classePostgreSQLContainer.Leitura Adicional:
A seguir, descobriremos como podemos executar um contêiner do Selenium WebDriver.
Executando um Contêiner do Selenium WebDriver
Se quisermos executar um contêiner do Selenium WebDriver, devemos utilizar a classe BrowserWebDriverContainer. Depois de criarmos um novo objeto BrowserWebDriverContainer, podemos configurar o navegador iniciado utilizando o método withCapabilities() (sucessor moderno do antigo withDesiredCapabilities()) da classe BrowserWebDriverContainer, passando uma instância de ChromeOptions ou FirefoxOptions.
Vamos escrever um método de teste que utiliza o Google Chrome em contêiner para abrir a página inicial do site deste curso. Podemos escrever esse método de teste seguindo estes passos:
Primeiro, devemos configurar a instância de BrowserWebDriverContainer que inicia o contêiner do Selenium WebDriver. Ao criar um novo objeto BrowserWebDriverContainer, garantimos que o contêiner inicie o navegador web Chrome através de ChromeOptions().
Após configurarmos o BrowserWebDriverContainer, o código-fonte da nossa classe de teste fica da seguinte forma:
|
|
Segundo, devemos escrever um método de teste que abre a página inicial do site deste curso utilizando uma instância de RemoteWebDriver conectada ao endereço Selenium do contêiner e garante que a página aberta possua o título correto.
Após escrevermos esse método de teste, o código-fonte da nossa classe de teste fica da seguinte forma:
|
|
[!NOTE] Como podemos ver, podemos conectar o
RemoteWebDriverinvocandochrome.seleniumAddress(ouchrome.webDrivernas APIs clássicas) da classeBrowserWebDriverContainer.Leitura Adicional:
Agora já sabemos como obter as dependências necessárias com o Gradle e utilizar os modos de uso mais comuns da biblioteca Testcontainers. Vamos resumir o que aprendemos nesta aula.
Resumo
Esta aula nos ensinou cinco coisas:
- O Testcontainers nos permite iniciar um contêiner Docker antes da execução dos nossos métodos de teste e encerrá-lo após os nossos métodos de teste terem sido executados.
- Na stack moderna, o Testcontainers 2.0.5 requer um ambiente Docker compatível, Java 21+ / Kotlin 2.4.21 e JUnit Jupiter 6.0.3 (com
@Testcontainerse@Container). - Se quisermos executar um contêiner genérico, devemos utilizar a classe
GenericContainer. - Se quisermos executar um contêiner que rode um banco de dados PostgreSQL, devemos utilizar a classe
PostgreSQLContainer. - Se quisermos executar um contêiner do Selenium WebDriver, devemos utilizar a classe
BrowserWebDriverContainer.