Integração com TestContainers no Springboot 3.1
O Springboot framework é um dos frameworks mais populares do mercado, como solução para criação de arquiteturas baseadas em microsserviços. Com a constante evolução do Java, que acaba de lançar a release da versão 17, o projeto Springboot lança uma nova versão para acompanhar as evoluções da linguagem.
Agora em sua versão 3.1, o Springboot apresenta algumas novidades interessantes, como:
- Uso do Mockito 5 para facilitar os testes;
- Upgrade para Hibernate 6.2;
- Integração com TestContainers;
- Uso de Docker-compose para facilitar a criação de ambientes;
Neste artigo iremos focar apenas na integração usando TestContainers.
TestContainers é uma biblioteca que fornece, de forma eficiente e descartável, instâncias de banco de dados, Selenium, Message Brokers ou quaisquer dependências necessárias que possam ser executadas usando docker. Isto é útil, por exemplo, para auxiliar no desenvolvimento de testes de integração.
Na nova versão do Springboot 3.1, agora é possível configurar o TestContainers para que possa disponibilizar recursos externos em tempo de desenvolvimento, como por exemplo, um banco de dados.
Neste artigo iremos usar o TestContainers para criar um ambiente local de desenvolvimento que disponibiliza um serviço de banco de dados PostgreSQL.
Para isso, é preciso adicionar as seguintes dependências no pom.xml
:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>test</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-testcontainers</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>postgresql</artifactId>
<scope>test</scope>
</dependency>
Dica: O Spring Initializr pode ser utilizado para adicionar essas dependências ao criar um projeto do zero.
TestContainers para desenvolvimento local
Para conseguirmos configurar a aplicação e usar TestContainers para auxiliar o desenvolvimento local, será preciso criar um novo arquivo. Vamos chamá-lo de TestContainersConfigurationApplication.
Esse arquivo será utilizado para inicializar a aplicação e o TestContainers com o PostgreSQL. A implementação da classe java e uma explicação sobre a responsabilidade de cada método pode ser observada abaixo:
/**
* Test application to integrate with TestContainers for local development
*/
@Configuration
public class TestContainersConfigurationApplication {
public static void main(String[] args) {
SpringApplication.from(TestcontainersExampleApplication::main)
.with(TestContainersConfigurationApplication.class)
.run(args);
}
@Bean
@RestartScope
// springboot-devtools must be in test scope. This annotation will keep
// the container up and running even if the application restarts
@ServiceConnection
PostgreSQLContainer createPostgresSQLContainer() {
return new PostgreSQLContainer("postgres");
}
}
Como de costume no uso do Springboot, o método main()
é utilizado para inicializar a aplicação principal TestcontainersExampleApplication
. O que diferencia nesse caso é a configuração do TestContainers utilizando o método .with()
, que no caso é a nossa classe de configuração TestContainerConfigurationApplication
com o TestContainers.
No método createPostgresSQLContainer()
é onde toda a magia acontece. O método, que utiliza a combinação das anotações @Bean
, @RestartScope
e @ServiceConnection
é responsável por inicializar o container do PostgreSQL e garantir que ele esteja disponível antes da aplicação ser iniciada. Desta maneira, o serviço de banco de dados pode ser utilizado pela aplicação.
O recurso do Spring, a anotação @Bean
é responsável por injetar o bean antes da aplicação inicializar, e que esse recurso pode ser injetado em qualquer outro bean na aplicação.
A anotação @RestartScope
é uma anotação da dependência spring-boot-devtools
que define que um bean se manterá inicializado, mesmo que a aplicação seja reiniciada. Isso permite que, mesmo se a aplicação for recarregada em tempo de desenvolvimento, o container vai se manter funcionando no mesmo estado, independentemente se a aplicação reiniciar.
Por fim, a anotação @ServiceConnection
estabelece que Springboot possa estabelecer uma conexão automática com o serviço que está sendo executado no container. O ciclo de vida do container agora é gerenciado pelo Springboot, ou seja, o container é executado quando a aplicação inicializar e desligado quando a aplicação finalizar.
Ah! Mas eu utilizo imagens específicas no trabalho, que nunca existirão implementações dela no TestContainers. Não será possível utilizá-las, então?
Para esses cenários, o TestContainer disponibiliza a classe GenericContainer
, que permite configurar e utilizar qualquer nome de imagem, incluindo a configuração de portas, redes, volumes, acesso a logs e até executar comandos dentro do container, similar ao que é permitido quando é utilizado o serviço Docker, como segue:
@Testcontainers
public abstract class AbstractIntegrationTest {
@Container
static GenericContainer container = new GenericContainer(DockerImageName.parse("myImage/myImage"))
.withEnv("MY_ENV", "value")
.withExposedPorts(9999)
.withNetworkAliases("myNetworkAlias");
@DynamicPropertySource
public static void myProperties(DynamicPropertyRegistry registry) {
registry.add("myimage.mail.host", container::getHost);
registry.add("myimage.mail.port", container::getFirstMappedPort);
}
}
No exemplo acima, a classe GenericContainer
realiza as seguintes configurações:
- Utiliza a a imagem
myImage/myImage
; - Define uma variável de ambiente chamada
MY_ENV=value
; - Expõe a porta
9999
; - Define a rede
myNetworkAlias
.
Para mais detalhes sobre as possíveis configurações, você pode consultar a documentação da API.
No dia da publicação deste artigo, apenas os containers abaixo eram suportados nativamente pelo TestContainers:
CassandraContainer
CouchbaseContainer
ElasticsearchContainer
GenericContainer
usandoredis
ouopenzipkin/zipkin
JdbcDatabaseContainer
KafkaContainer
MongoDBContainer
MariaDBContainer
MSSQLServerContainer
MySQLContainer
Neo4jContainer
OracleContainer
PostgreSQLContainer
RabbitMQContainer
RedpandaContainer
Considerações finais
TestContainers possui uma API consistente que pode ajudar no gerenciamento de containers para seu ambiente local de desenvolvimento e para testes de integração, ajudando a isolar recursos externos dos seus testes, melhorando o código e levando a testes mais robustos e confiáveis. Desta forma, o desenvolvedor consegue ter um foco maior na implementação das regras de negócios.
Caso queira saber mais informações sobre TestContainers, visite o site oficial ou no repositório do GitHub.