Pular para o conteúdo principal

victorstein.dev

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.