Neste artigo, demonstraremos como usar a biblioteca TestContainers em um projeto Golang para executar testes de integração com um banco de dados PostgreSQL. Vamos criar um teste que verifica a conexão com o banco de dados usando um contêiner Docker.
Importando bibliotecas necessárias
Primeiramente, importamos as bibliotecas necessárias para criar e gerenciar contêineres Docker e realizar testes usando a biblioteca testify
:
import (
"context"
"database/sql"
"fmt"
_ "github.com/lib/pq"
"github.com/stretchr/testify/assert"
"github.com/testcontainers/testcontainers-go"
"github.com/testcontainers/testcontainers-go/wait"
"testing"
"time"
)
Função para criar e configurar contêiner Docker
Em seguida, criamos a função criarConfigurarContainer
que recebe a imagem Docker, as portas expostas e as variáveis de ambiente como parâmetros e retorna um contêiner configurado e iniciado:
func criarConfigurarContainer(imagemDocker string, portasExpostas []string, variaveisAmbiente map[string]string) (testcontainers.Container, error) {
// Configuração do contêiner
containerRequest := testcontainers.ContainerRequest{
Image: imagemDocker,
ExposedPorts: portasExpostas,
Env: variaveisAmbiente,
WaitingFor: wait.ForLog("database system is ready to accept connections").WithStartupTimeout(2 * time.Second),
}
// Criação do contêiner
conteiner, err := testcontainers.GenericContainer(context.Background(), testcontainers.GenericContainerRequest{
ContainerRequest: containerRequest,
Started: true,
})
if err != nil {
return nil, fmt.Errorf("erro ao criar o contêiner: %w", err)
}
return conteiner, nil
}
A função criarConfigurarContainer
é responsável por criar e configurar um contêiner Docker utilizando a biblioteca Testcontainers-Go. Ela aceita três parâmetros:
imagemDocker
: a string contendo o nome da imagem Docker que será utilizada para criar o contêiner.portasExpostas
: uma lista de strings representando as portas que serão expostas pelo contêiner.variaveisAmbiente
: um mapa (map) onde a chave é a string representando o nome da variável de ambiente e o valor é a string representando o valor da variável de ambiente.
A função inicia definindo a estrutura testcontainers.ContainerRequest
com as informações fornecidas nos parâmetros. Essa estrutura é usada para configurar o contêiner Docker, incluindo a imagem Docker, as portas expostas e as variáveis de ambiente. A propriedade WaitingFor
é configurada com um log específico e um tempo limite de inicialização (2 segundos) para garantir que o contêiner esteja pronto para aceitar conexões antes de prosseguir.
Em seguida, a função testcontainers.GenericContainer
é chamada com um contexto e um objeto testcontainers.GenericContainerRequest
. A função cria o contêiner Docker com base na configuração fornecida (containerRequest
) e inicia-o (propriedade Started
definida como true
).
Se ocorrer algum erro durante a criação do contêiner, a função retorna nil
e um erro formatado com informações sobre o problema encontrado. Caso contrário, a função retorna a instância do contêiner criado e nil
para o erro.
Essa função é útil para simplificar a criação e configuração de contêineres Docker em testes de integração, permitindo que você se concentre no teste em si em vez de lidar com a configuração do Docker.
Teste de integração com o PostgreSQL
Agora, criamos a função de teste TestRepository_Ping
, que utiliza a função criarConfigurarContainer
para iniciar um contêiner PostgreSQL e testar a conexão com o banco de dados:
func TestRepository_Ping(t *testing.T) {
portasExpostas := []string{"5432/tcp"}
variaveisAmbiente := map[string]string{"POSTGRES_PASSWORD": "postgres", "POSTGRES_DB": "postgres", "POSTGRES_USER": "postgres"}
container, err := criarConfigurarContainer("docker.io/postgres:15.2-alpine", portasExpostas, variaveisAmbiente)
if err != nil {
t.Fatalf("erro ao criar o contêiner: %s", err)
}
defer func(container testcontainers.Container, ctx context.Context) {
err := container.Terminate(ctx)
if err != nil {
t.Fatalf("erro ao finalizar o contêiner: %s", err)
}
}(container, context.Background())
Após criar e configurar o contêiner PostgreSQL, obtemos a porta mapeada no host e estabelecemos a conexão com o banco de dados:
portaMapeada, err := container.MappedPort(context.Background(), "5432/tcp")
if err != nil {
t.Fatalf("erro ao obter a porta mapeada: %s", err)
}
db, err := sql.Open("postgres", fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s sslmode=disable", "localhost", portaMapeada.Port(), "postgres", "postgres", "postgres"))
if err != nil {
t.Fatalf("erro ao conectar no banco de dados: %s", err)
}
Com a conexão estabelecida, criamos uma instância do repositório e executamos o método Ping()
para verificar a conexão com o banco de dados:
repository := NewRepository(db)
time.Sleep(1 * time.Second)
err2 := repository.Ping()
defer db.Close()
assert.NoError(t, err2)
}
Esse trexo do teste realiza as seguintes ações:
repository := NewRepository(db)
: Uma nova instância de um repositório é criada usando a funçãoNewRepository
, que recebe como parâmetro a conexão de banco de dadosdb
previamente estabelecida.time.Sleep(1 * time.Second)
: Uma pausa de 1 segundo é introduzida antes de executar o teste. Essa pausa pode ser necessária para garantir que o contêiner Docker e o banco de dados estejam prontos para aceitar conexões e realizar operações antes de prosseguir com o teste.err2 := repository.Ping()
: A funçãoPing
é chamada no repositório para verificar se a conexão com o banco de dados está funcionando corretamente. Se houver algum problema na conexão, a função retornará um erro (err2
).defer db.Close()
: A funçãoClose
é chamada com a palavra-chavedefer
para garantir que a conexão com o banco de dados seja fechada após a conclusão do teste, independentemente do resultado. A palavra-chavedefer
em Go permite programar a execução de uma função para o momento em que a função atual retorna. Isso é útil para garantir que os recursos sejam liberados corretamente.assert.NoError(t, err2)
: A funçãoNoError
do pacoteassert
é usada para verificar se a funçãoPing
não retornou nenhum erro. Se a funçãoPing
retornou um erro, o teste falhará com uma mensagem informando o erro retornado.
Conclusão
Neste artigo, vimos como usar a biblioteca TestContainers em um projeto Golang para executar testes de integração com um banco de dados PostgreSQL em um contêiner Docker. O exemplo demonstrou como criar e configurar um contêiner, estabelecer a conexão com o banco de dados e verificar a conexão utilizando um teste Golang.
A utilização de TestContainers é uma solução prática e eficiente para executar testes de integração, pois permite a criação e configuração de ambientes isolados e controlados, facilitando o desenvolvimento e a verificação da integração entre componentes de software.
O código utilizado nesse artigo encontra-se aqui