Build and maintain Go integration tests using testcontainers-go with deterministic setup, readiness checks, isolated test data, and reliable cleanup. Use for new integration test suites, migrating flaky external-service tests, or adding database/queue/cache coverage in Go services. Use proactively when tests depend on Postgres, Redis, Kafka, or any service that should run in containers. Examples: - user: "Add integration tests for our repository layer" -> start a Postgres container, run migrations, verify CRUD paths - user: "Our Redis tests are flaky" -> add readiness strategy, per-test isolation, and cleanup hooks - user: "Test against real dependencies locally and in CI" -> wire container lifecycle to go test and CI-friendly defaults - user: "Move docker-compose tests to Go" -> replace ad-hoc scripts with testcontainers-go fixtures
Use this workflow when authoring *_integration_test.go or package-local integration tests that require real dependencies.
package repository_test
import (
"context"
"database/sql"
"fmt"
"testing"
"time"
_ "github.com/jackc/pgx/v5/stdlib"
"github.com/testcontainers/testcontainers-go"
"github.com/testcontainers/testcontainers-go/wait"
)
func TestUserRepository_Integration(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
defer cancel()
req := testcontainers.ContainerRequest{
Image: "postgres:16-alpine",
ExposedPorts: []string{"5432/tcp"},
Env: map[string]string{
"POSTGRES_DB": "app",
"POSTGRES_USER": "app",
"POSTGRES_PASSWORD": "app",
},
WaitingFor: wait.ForLog("database system is ready to accept connections").WithStartupTimeout(60 * time.Second),
}
postgres, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{
ContainerRequest: req,
Started: true,
})
if err != nil {
t.Fatalf("start postgres container: %v", err)
}
t.Cleanup(func() {
_ = postgres.Terminate(context.Background())
})
host, err := postgres.Host(ctx)
if err != nil {
t.Fatalf("postgres host: %v", err)
}
port, err := postgres.MappedPort(ctx, "5432")
if err != nil {
t.Fatalf("postgres mapped port: %v", err)
}
dsn := fmt.Sprintf("postgres://app:app@%s:%s/app?sslmode=disable", host, port.Port())
db, err := sql.Open("pgx", dsn)
if err != nil {
t.Fatalf("open db: %v", err)
}
t.Cleanup(func() { _ = db.Close() })
if err := db.PingContext(ctx); err != nil {
t.Fatalf("ping db: %v", err)
}
// Run migrations/fixtures, then execute integration assertions.
}
postgres:16-alpine), not latest.context.WithTimeout to bound startup and test duration.t.Cleanup for container termination and resource cleanup.wait.ForLog, wait.ForListeningPort, health checks).t.Logf) for container startup/debug failures.go fmt ./...
go test ./...