84/100 Dias de Golang - Testes de Integração em Go com Testcontainers
Table of Contents
#
Testes de Integração em Go com Testcontainers
Mockar para simular bancos de dados e integrações com serviços externos é muito útil e muito rápido, mas esses testes não garantem que a aplicação irá funcionar com o serviço real. É nesse contexto que o Testcontainers se faz útil. Com ele podemos gerar testes de integração com as dependências “reais”. Ele funciona da seguinte forma: definimos um container (imagem, portas, envs, …), o testcontainers sobe essa infraestrutura, realizamos os testes e o container é removido no final da execução.
Vamos criar um projeto, instalar as dependências e escrever alguns testes.
Para instalar o testcontainers:
go get github.com/testcontainers/testcontainers-go
Vamos subir um container postgresql, criar uma tabela e fazer uma consulta.
package integration_test
import (
"context"
"fmt"
"testing"
"time"
"github.com/jackc/pgx/v5"
tc "github.com/testcontainers/testcontainers-go"
"github.com/testcontainers/testcontainers-go/wait"
)
func TestPostgresIntegration(t *testing.T) {
ctx := context.Background()
req := tc.ContainerRequest{
Image: "postgres:15",
ExposedPorts: []string{"5432/tcp"},
Env: map[string]string{
"POSTGRES_USER": "test",
"POSTGRES_PASSWORD": "test",
"POSTGRES_DB": "testdb",
},
WaitingFor: wait.ForListeningPort("5432/tcp").WithStartupTimeout(30 * time.Second),
}
postgresC, err := tc.GenericContainer(ctx, tc.GenericContainerRequest{
ContainerRequest: req,
Started: true,
})
if err != nil {
t.Fatalf("Erro ao iniciar container: %v", err)
}
defer postgresC.Terminate(ctx)
host, err := postgresC.Host(ctx)
if err != nil {
t.Fatal(err)
}
port, err := postgresC.MappedPort(ctx, "5432")
if err != nil {
t.Fatal(err)
}
dsn := fmt.Sprintf("postgres://test:test@%s:%s/testdb", host, port.Port())
conn, err := pgx.Connect(ctx, dsn)
if err != nil {
t.Fatalf("Erro ao conectar no Postgres: %v", err)
}
defer conn.Close(ctx)
_, err = conn.Exec(ctx, `
CREATE TABLE IF NOT EXISTS users (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL
);
`)
if err != nil {
t.Fatalf("Erro ao criar tabela: %v", err)
}
_, err = conn.Exec(ctx, `INSERT INTO users (name) VALUES ($1)`, "Maria")
if err != nil {
t.Fatalf("Erro ao inserir dado: %v", err)
}
var name string
err = conn.QueryRow(ctx, `SELECT name FROM users WHERE id=1`).Scan(&name)
if err != nil {
t.Fatalf("Erro ao consultar dado: %v", err)
}
if name != "Maria" {
t.Errorf("Esperado 'Maria', obtido: %s", name)
}
}
O início do da função é somente o setup do testcontainer, esse código pode ser reutilizado em outros testes. Algumas coisas interessantes: wait.ForListeningPort
aguarda o banco estar pronto para conexões, lembrar sempre de usar o defer postgresC.Terminate(ctx)
para fechar a conexão.