Nos dias atuais, cada vez mais nos integramos com diversas ferramentas, podendo ser elas um banco de dados (ex: MySQL, Redis, Mongo), um servidor web/proxy (Ex: nginx, Apache) ou até mesmo algum outro serviço que é desenvolvido pelo próprio time ou empresa. Muitas vezes apenas queremos subir alguma dessas dependências, executar os nossos testes e descer as mesmas para que não fique consumindo recursos sem necessidade.
Com o surgimento do Docker, conseguimos tornar mais fácil nossa vida, empacotando cada dependência em um container, iniciando ele e depois parando, mas ainda assim é necessário ter um gerenciamento manual do ciclo de vida desses containers. Para que seja possível realizar a comunicação com os containers, ainda é necessário recuperar o IP ou a porta aberta e passar para os nossos testes para que seja possível executar.
Para resolver todos esses nosso problemas, podemos utilizar o Testcontainer, uma incrível biblioteca Java que controla todo esse ciclo de vida dos containers dockers e que possui uma integração com JUnit, facilitando ainda mais o nosso trabalho. O Testcontainer possui dois tipos de containers que você pode trabalhar com ele, o container genérico e o container específico que vamos ver em seguida:
Antes de qualquer coisa precisamos importar as dependências em nosso projeto.
<dependency> <groupId>org.testcontainers</groupId> <artifactId>testcontainers</artifactId> <version>1.8.3</version> </dependency>
Container Genérico
Como o próprio nome já diz, é um container genérico, sendo mais flexível e que aceita qualquer imagem que você especifique para ele, como um serviço desenvolvido interno. Como exemplo, vamos utilizar o da própria documentação.
@ClassRule public static GenericContainer alpine = new GenericContainer("alpine:3.2") .withExposedPorts(80) .withEnv("MAGIC_NUMBER", "42") .withCommand("/bin/sh", "-c", "while true; do echo \"$MAGIC_NUMBER\" | nc -l -p 80; done");
Estamos criando uma @ClassRule do próprio JUnit que irá iniciar o container antes dos testes e destruí-lo após todos os testes, mas também é possível utilizar a anotação @Rule que irá criar uma nova instância do container antes da execução de cada teste.
Na construção de um GenericContainer nós passamos qual será a imagem base para a criação do container e após a construção é possível informar algumas configurações como portas que serão expostas pelo container, variáveis de ambiente, comando inicial que será executado e muitos outros. Lembrando que são as mesmas configurações de quando criamos um container através do docker.
Após o container ser inicializado, é possível recuperar algumas informações, como o IP e porta mapeada, que seriam as informações necessárias caso nosso container fosse um banco de dados ou um servidor web. Vamos utilizar um container genérico de Redis como exemplo.
@ClassRule public static GenericContainer redis = new GenericContainer("redis:4") .withExposedPorts(6379); @Test public void getConnectionInfoFromRedis() { String redisUrl = redis.getContainerIpAddress() + ":" + redis.getMappedPort(6379); }
Container Específico
Os containers específicos herdam de GenericContainer, com uma única diferença que ao utiliza-los você está dizendo que irá trabalhar com um container específico e irá ganhar de brinde alguns métodos a mais para recuperar informações como o usuário, senha e URL de conexão do banco de dados, mas que irá variar de acordo com o container que você está trabalhando, pois cada um terá sua peculiaridade.
@Rule public MySQLContainer mysql = new MySQLContainer(); @Test public void getMySQLInfos() { mysql.getJdbcUrl(); mysql.getUsername(); mysql.getPassword(); }
Por ser algo específico, é necessário importar as dependências de cada um que for utilizar.
<dependency> <groupId>org.testcontainers</groupId> <artifactId>mysql</artifactId> <version>1.8.3</version> </dependency>
Essa é uma pequena introdução do Testcontainer que possui muitas outras funcionalidades além dessas apresentadas, mas já podemos perceber que isso nos ajuda de mais no dia a dia para a execução de testes de integração, evitando até mesmo aquela velha história: “Na minha máquina funciona”.