testcontainers-go

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Testcontainers for Go Integration Testing

Testcontainers for Go 集成测试指南

A comprehensive guide for using Testcontainers for Go to write reliable integration tests with Docker containers in Go projects.
一份关于在Go项目中使用Testcontainers for Go结合Docker容器编写可靠集成测试的全面指南。

Description

简介

This skill helps you write integration tests using Testcontainers for Go, a Go library that provides lightweight, throwaway instances of common databases, message queues, web browsers, or anything that can run in a Docker container.
Key capabilities:
  • Use 62+ pre-configured modules for common services (databases, message queues, cloud services, etc.)
  • Set up and manage Docker containers in Go tests
  • Configure networking, volumes, and environment variables
  • Implement proper cleanup and resource management
  • Debug and troubleshoot container issues
本指南将帮助你使用Testcontainers for Go编写集成测试,这是一个Go语言库,可提供轻量、一次性的常见数据库、消息队列、浏览器或任何可在Docker容器中运行的服务实例。
核心功能:
  • 使用62+个针对常见服务(数据库、消息队列、云服务等)的预配置模块
  • 在Go测试中搭建和管理Docker容器
  • 配置网络、卷和环境变量
  • 实现正确的清理和资源管理
  • 调试和排查容器问题

When to Use This Skill

适用场景

Use this skill when you need to:
  • Write integration tests that require real services (databases, message queues, etc.)
  • Test against multiple versions or configurations of dependencies
  • Create reproducible test environments
  • Avoid mocking external dependencies in integration tests
  • Set up ephemeral test infrastructure
在以下场景中使用本指南:
  • 需要编写依赖真实服务(数据库、消息队列等)的集成测试时
  • 针对依赖项的多个版本或配置进行测试时
  • 创建可复现的测试环境时
  • 避免在集成测试中模拟外部依赖项时
  • 搭建临时测试基础设施时

Prerequisites

前置条件

  • Docker or Podman installed and running
  • Go 1.24+ (check
    go.mod
    for project-specific requirements)
  • Docker socket accessible at standard locations (Docker Desktop on macOS/Windows,
    /var/run/docker.sock
    on Linux)
  • 已安装并运行Docker或Podman
  • Go 1.24+(可查看项目的
    go.mod
    获取具体要求)
  • Docker套接字可在标准路径访问(macOS/Windows为Docker Desktop,Linux为
    /var/run/docker.sock

Instructions

使用步骤

1. Installation & Setup

1. 安装与配置

Add testcontainers-go to your project:
bash
go get github.com/testcontainers/testcontainers-go
For pre-configured modules (recommended):
bash
undefined
将testcontainers-go添加到你的项目中:
bash
go get github.com/testcontainers/testcontainers-go
如需使用预配置模块(推荐):
bash
undefined

Example: PostgreSQL module

示例:PostgreSQL模块

go get github.com/testcontainers/testcontainers-go/modules/postgres
go get github.com/testcontainers/testcontainers-go/modules/postgres

Example: Kafka module

示例:Kafka模块

go get github.com/testcontainers/testcontainers-go/modules/kafka
go get github.com/testcontainers/testcontainers-go/modules/kafka

Example: Redis module

示例:Redis模块

go get github.com/testcontainers/testcontainers-go/modules/redis

**Verify Docker availability:**

```go
func TestDockerAvailable(t *testing.T) {
    testcontainers.SkipIfProviderIsNotHealthy(t)
    // Test will skip if Docker is not running
}

go get github.com/testcontainers/testcontainers-go/modules/redis

**验证Docker可用性:**

```go
func TestDockerAvailable(t *testing.T) {
    testcontainers.SkipIfProviderIsNotHealthy(t)
    // 如果Docker未运行,测试将自动跳过
}

2. Using Pre-Configured Modules (Recommended Approach)

2. 使用预配置模块(推荐方式)

Testcontainers for Go provides 62+ pre-configured modules that offer production-ready configurations, sensible defaults, and helper methods. Always prefer modules over generic containers when available.
Testcontainers for Go提供62+个预配置模块,这些模块具备生产就绪的配置、合理的默认值和辅助方法。只要有可用模块,就优先使用模块而非通用容器

Why Use Modules?

为什么使用模块?

  • Sensible defaults: Pre-configured ports, environment variables, and wait strategies
  • Connection helpers: Built-in methods like
    ConnectionString()
    ,
    Endpoint()
  • Specialized features: Module-specific functionality (e.g., Postgres snapshots, Kafka topic management)
  • Automatic credentials: Secure credential generation and management
  • Battle-tested: Used in production by thousands of projects
  • 合理默认值:预配置端口、环境变量和等待策略
  • 连接辅助方法:内置
    ConnectionString()
    Endpoint()
    等方法
  • 专属功能:模块特定功能(如PostgreSQL快照、Kafka主题管理)
  • 自动凭证管理:安全的凭证生成与管理
  • 久经考验:已被数千个生产项目使用

Available Module Categories

可用模块分类

Databases (17 modules):
  • postgres
    ,
    mysql
    ,
    mariadb
    ,
    mongodb
    ,
    redis
    ,
    valkey
  • cockroachdb
    ,
    clickhouse
    ,
    memcached
    ,
    influxdb
  • arangodb
    ,
    cassandra
    ,
    scylladb
    ,
    dynamodb
  • dolt
    ,
    databend
    ,
    surrealdb
Message Queues (6 modules):
  • kafka
    ,
    rabbitmq
    ,
    nats
    ,
    pulsar
    ,
    redpanda
    ,
    solace
Search & Vector Databases (9 modules):
  • elasticsearch
    ,
    opensearch
    ,
    meilisearch
  • weaviate
    ,
    qdrant
    ,
    chroma
    ,
    milvus
    ,
    vearch
    ,
    pinecone
Cloud & Infrastructure (6 modules):
  • gcloud
    ,
    azure
    ,
    azurite
    ,
    localstack
    ,
    dind
    ,
    k3s
Services & Tools (13 modules):
  • consul
    ,
    etcd
    ,
    neo4j
    ,
    couchbase
    ,
    vault
    ,
    openldap
  • artemis
    ,
    inbucket
    ,
    mockserver
    ,
    nebulagraph
    ,
    minio
  • toxiproxy
    ,
    aerospike
Development (10 modules):
  • compose
    ,
    registry
    ,
    k6
    ,
    ollama
    ,
    grafana-lgtm
  • dockermodelrunner
    ,
    dockermcpgateway
    ,
    socat
    ,
    mssql
数据库(17个模块):
  • postgres
    ,
    mysql
    ,
    mariadb
    ,
    mongodb
    ,
    redis
    ,
    valkey
  • cockroachdb
    ,
    clickhouse
    ,
    memcached
    ,
    influxdb
  • arangodb
    ,
    cassandra
    ,
    scylladb
    ,
    dynamodb
  • dolt
    ,
    databend
    ,
    surrealdb
消息队列(6个模块):
  • kafka
    ,
    rabbitmq
    ,
    nats
    ,
    pulsar
    ,
    redpanda
    ,
    solace
搜索与向量数据库(9个模块):
  • elasticsearch
    ,
    opensearch
    ,
    meilisearch
  • weaviate
    ,
    qdrant
    ,
    chroma
    ,
    milvus
    ,
    vearch
    ,
    pinecone
云与基础设施(6个模块):
  • gcloud
    ,
    azure
    ,
    azurite
    ,
    localstack
    ,
    dind
    ,
    k3s
服务与工具(13个模块):
  • consul
    ,
    etcd
    ,
    neo4j
    ,
    couchbase
    ,
    vault
    ,
    openldap
  • artemis
    ,
    inbucket
    ,
    mockserver
    ,
    nebulagraph
    ,
    minio
  • toxiproxy
    ,
    aerospike
开发工具(10个模块):
  • compose
    ,
    registry
    ,
    k6
    ,
    ollama
    ,
    grafana-lgtm
  • dockermodelrunner
    ,
    dockermcpgateway
    ,
    socat
    ,
    mssql

Basic Module Usage Pattern

模块基本使用模式

go
package myapp_test

import (
    "context"
    "testing"

    "github.com/stretchr/testify/require"
    "github.com/testcontainers/testcontainers-go"
    "github.com/testcontainers/testcontainers-go/modules/postgres"
)

func TestWithPostgres(t *testing.T) {
    ctx := context.Background()

    // Start PostgreSQL container with sensible defaults
    pgContainer, err := postgres.Run(ctx, "postgres:16-alpine")
    testcontainers.CleanupContainer(t, pgContainer)
    require.NoError(t, err)

    // Get connection string - credentials auto-generated
    connStr, err := pgContainer.ConnectionString(ctx)
    require.NoError(t, err)
    // connStr: "postgres://postgres:password@localhost:49153/postgres?sslmode=disable"

    // Use connection string with your database driver
    db, err := sql.Open("postgres", connStr)
    require.NoError(t, err)
    defer db.Close()

    // Run your tests...
}
go
package myapp_test

import (
    "context"
    "testing"

    "github.com/stretchr/testify/require"
    "github.com/testcontainers/testcontainers-go"
    "github.com/testcontainers/testcontainers-go/modules/postgres"
)

func TestWithPostgres(t *testing.T) {
    ctx := context.Background()

    // 使用合理默认值启动PostgreSQL容器
    pgContainer, err := postgres.Run(ctx, "postgres:16-alpine")
    testcontainers.CleanupContainer(t, pgContainer)
    require.NoError(t, err)

    // 获取连接字符串 - 凭证自动生成
    connStr, err := pgContainer.ConnectionString(ctx)
    require.NoError(t, err)
    // connStr: "postgres://postgres:password@localhost:49153/postgres?sslmode=disable"

    // 使用连接字符串连接数据库驱动
    db, err := sql.Open("postgres", connStr)
    require.NoError(t, err)
    defer db.Close()

    // 运行你的测试...
}

Module Configuration with Options

通过选项配置模块

Modules support three levels of customization:
Level 1: Simple Options (via testcontainers.CustomizeRequestOption)
go
pgContainer, err := postgres.Run(
    ctx,
    "postgres:16-alpine",
    testcontainers.WithEnv(map[string]string{
        "POSTGRES_DB": "myapp_test",
    }),
    testcontainers.WithLabels(map[string]string{
        "env": "test",
    }),
)
Level 2: Module-Specific Options
go
// PostgreSQL with init scripts
pgContainer, err := postgres.Run(
    ctx,
    "postgres:16-alpine",
    postgres.WithInitScripts("./testdata/init.sql"),
    postgres.WithDatabase("myapp_test"),
    postgres.WithUsername("custom_user"),
    postgres.WithPassword("custom_pass"),
)

// Redis with configuration
redisContainer, err := redis.Run(
    ctx,
    "redis:7-alpine",
    redis.WithSnapshotting(10, 1),
    redis.WithLogLevel(redis.LogLevelVerbose),
)

// Kafka with custom config
kafkaContainer, err := kafka.Run(
    ctx,
    "confluentinc/confluent-local:7.5.0",
    kafka.WithClusterID("test-cluster"),
)
Level 3: Advanced Configuration with Lifecycle Hooks
go
// PostgreSQL with custom initialization
pgContainer, err := postgres.Run(
    ctx,
    "postgres:16-alpine",
    postgres.WithDatabase("myapp"),
    testcontainers.WithLifecycleHooks(
        testcontainers.ContainerLifecycleHooks{
            PostStarts: []testcontainers.ContainerHook{
                func(ctx context.Context, c testcontainers.Container) error {
                    // Custom initialization after container starts
                    return nil
                },
            },
        },
    ),
)
模块支持三个层级的自定义配置:
层级1:简单选项(通过testcontainers.CustomizeRequestOption)
go
pgContainer, err := postgres.Run(
    ctx,
    "postgres:16-alpine",
    testcontainers.WithEnv(map[string]string{
        "POSTGRES_DB": "myapp_test",
    }),
    testcontainers.WithLabels(map[string]string{
        "env": "test",
    }),
)
层级2:模块专属选项
go
// 带初始化脚本的PostgreSQL
pgContainer, err := postgres.Run(
    ctx,
    "postgres:16-alpine",
    postgres.WithInitScripts("./testdata/init.sql"),
    postgres.WithDatabase("myapp_test"),
    postgres.WithUsername("custom_user"),
    postgres.WithPassword("custom_pass"),
)

// 带配置的Redis
redisContainer, err := redis.Run(
    ctx,
    "redis:7-alpine",
    redis.WithSnapshotting(10, 1),
    redis.WithLogLevel(redis.LogLevelVerbose),
)

// 带自定义配置的Kafka
kafkaContainer, err := kafka.Run(
    ctx,
    "confluentinc/confluent-local:7.5.0",
    kafka.WithClusterID("test-cluster"),
)
层级3:带生命周期钩子的高级配置
go
// 带自定义初始化的PostgreSQL
pgContainer, err := postgres.Run(
    ctx,
    "postgres:16-alpine",
    postgres.WithDatabase("myapp"),
    testcontainers.WithLifecycleHooks(
        testcontainers.ContainerLifecycleHooks{
            PostStarts: []testcontainers.ContainerHook{
                func(ctx context.Context, c testcontainers.Container) error {
                    // 容器启动后的自定义初始化操作
                    return nil
                },
            },
        },
    ),
)

Module-Specific Helper Methods

模块专属辅助方法

Most modules provide convenience methods beyond
ConnectionString()
:
go
// PostgreSQL: Snapshot & Restore for test isolation
func TestDatabaseIsolation(t *testing.T) {
    ctx := context.Background()

    pgContainer, err := postgres.Run(ctx, "postgres:16-alpine")
    testcontainers.CleanupContainer(t, pgContainer)
    require.NoError(t, err)

    connStr, _ := pgContainer.ConnectionString(ctx)
    db, _ := sql.Open("postgres", connStr)
    defer db.Close()

    // Create initial data
    db.Exec("CREATE TABLE users (id SERIAL PRIMARY KEY, name TEXT)")
    db.Exec("INSERT INTO users (name) VALUES ('Alice')")

    // Take snapshot
    err = pgContainer.Snapshot(ctx, postgres.WithSnapshotName("initial"))
    require.NoError(t, err)

    // Make changes
    db.Exec("INSERT INTO users (name) VALUES ('Bob')")

    // Restore to snapshot
    err = pgContainer.Restore(ctx, postgres.WithSnapshotName("initial"))
    require.NoError(t, err)

    // Bob is gone, only Alice remains
}

// Kafka: Get bootstrap servers
kafkaContainer, _ := kafka.Run(ctx, "confluentinc/confluent-local:7.5.0")
brokers, _ := kafkaContainer.Brokers(ctx)
大多数模块除了
ConnectionString()
外,还提供了便捷方法:
go
// PostgreSQL:用于测试隔离的快照与恢复
func TestDatabaseIsolation(t *testing.T) {
    ctx := context.Background()

    pgContainer, err := postgres.Run(ctx, "postgres:16-alpine")
    testcontainers.CleanupContainer(t, pgContainer)
    require.NoError(t, err)

    connStr, _ := pgContainer.ConnectionString(ctx)
    db, _ := sql.Open("postgres", connStr)
    defer db.Close()

    // 创建初始数据
    db.Exec("CREATE TABLE users (id SERIAL PRIMARY KEY, name TEXT)")
    db.Exec("INSERT INTO users (name) VALUES ('Alice')")

    // 创建快照
    err = pgContainer.Snapshot(ctx, postgres.WithSnapshotName("initial"))
    require.NoError(t, err)

    // 修改数据
    db.Exec("INSERT INTO users (name) VALUES ('Bob')")

    // 恢复到快照状态
    err = pgContainer.Restore(ctx, postgres.WithSnapshotName("initial"))
    require.NoError(t, err)

    // Bob的数据已消失,仅保留Alice的数据
}

// Kafka:获取引导服务器
kafkaContainer, _ := kafka.Run(ctx, "confluentinc/confluent-local:7.5.0")
brokers, _ := kafkaContainer.Brokers(ctx)

Finding the Right Module

如何找到合适的模块

  1. Browse available modules: https://testcontainers.com/modules/?language=go (complete, up-to-date list)
  2. Check the modules directory:
    /modules/
    in the testcontainers-go GitHub repository
  3. Module documentation: https://golang.testcontainers.org/modules/ (online docs for each module)
  4. Browse by category (see lists above)
  5. Search for examples: Each module has
    examples_test.go
    in its directory
Module location pattern:
github.com/testcontainers/testcontainers-go/modules/<module-name>

  1. 浏览可用模块https://testcontainers.com/modules/?language=go(完整、实时更新的列表)
  2. 查看modules目录testcontainers-go GitHub仓库中的
    /modules/
    目录
  3. 模块文档https://golang.testcontainers.org/modules/(每个模块的在线文档)
  4. 按分类浏览(见上方分类列表)
  5. 搜索示例:每个模块的目录中都有
    examples_test.go
    文件
模块路径模式:
github.com/testcontainers/testcontainers-go/modules/<module-name>

3. Using Generic Containers (Fallback)

3. 使用通用容器(备选方案)

When no pre-configured module exists, use generic containers.
IMPORTANT: Always add a wait strategy when exposing ports to ensure the container is ready before tests run. This is critical for reliability, especially in CI environments. Never use
time.Sleep
as a substitute - it's an anti-pattern that leads to flaky tests.
go
func TestCustomContainer(t *testing.T) {
    ctx := context.Background()

    ctr, err := testcontainers.Run(
        ctx,
        "custom-image:latest",
        testcontainers.WithExposedPorts("8080/tcp"),
        testcontainers.WithEnv(map[string]string{
            "APP_ENV": "test",
        }),
        // CRITICAL: Always add wait strategy for exposed ports
        testcontainers.WithWaitStrategy(
            wait.ForListeningPort("8080/tcp").WithStartupTimeout(time.Second*30),
        ),
    )
    testcontainers.CleanupContainer(t, ctr)
    require.NoError(t, err)

    // Get endpoint
    endpoint, err := ctr.Endpoint(ctx, "http")
    require.NoError(t, err)
}
Common generic container options:
go
testcontainers.Run(
    ctx,
    "image:tag",

    // Ports
    testcontainers.WithExposedPorts("80/tcp", "443/tcp"),

    // Environment
    testcontainers.WithEnv(map[string]string{
        "KEY": "value",
    }),

    // Files
    testcontainers.WithFiles(testcontainers.ContainerFile{
        Reader:            strings.NewReader("content"),
        ContainerFilePath: "/app/config.yml",
        FileMode:          0o644,
    }),

    // Volumes
    testcontainers.WithHostConfigModifier(func(hc *container.HostConfig) {
        hc.Binds = []string{"/host/path:/container/path"}
    }),

    // Wait strategies (REQUIRED when using WithExposedPorts)
    // Use wait.ForListeningPort for reliability - never use time.Sleep!
    testcontainers.WithWaitStrategy(
        wait.ForListeningPort("80/tcp"),
        // Or use other strategies: wait.ForLog(), wait.ForHTTP(), etc.
    ),

    // Commands
    testcontainers.WithAfterReadyCommand(
        testcontainers.NewRawCommand([]string{"echo", "initialized"}),
    ),

    // Labels
    testcontainers.WithLabels(map[string]string{
        "app": "myapp",
    }),
)

当没有可用的预配置模块时,可使用通用容器。
重要提示:暴露端口时务必添加等待策略,以确保容器在测试运行前完全就绪。这对于测试可靠性至关重要,尤其是在CI环境中。绝对不要用
time.Sleep()
替代等待策略——这是一种会导致测试不稳定的反模式。
go
func TestCustomContainer(t *testing.T) {
    ctx := context.Background()

    ctr, err := testcontainers.Run(
        ctx,
        "custom-image:latest",
        testcontainers.WithExposedPorts("8080/tcp"),
        testcontainers.WithEnv(map[string]string{
            "APP_ENV": "test",
        }),
        // 关键:暴露端口时务必添加等待策略
        testcontainers.WithWaitStrategy(
            wait.ForListeningPort("8080/tcp").WithStartupTimeout(time.Second*30),
        ),
    )
    testcontainers.CleanupContainer(t, ctr)
    require.NoError(t, err)

    // 获取端点
    endpoint, err := ctr.Endpoint(ctx, "http")
    require.NoError(t, err)
}
常见通用容器选项:
go
testcontainers.Run(
    ctx,
    "image:tag",

    // 端口
    testcontainers.WithExposedPorts("80/tcp", "443/tcp"),

    // 环境变量
    testcontainers.WithEnv(map[string]string{
        "KEY": "value",
    }),

    // 文件
    testcontainers.WithFiles(testcontainers.ContainerFile{
        Reader:            strings.NewReader("content"),
        ContainerFilePath: "/app/config.yml",
        FileMode:          0o644,
    }),

    // 卷
    testcontainers.WithHostConfigModifier(func(hc *container.HostConfig) {
        hc.Binds = []string{"/host/path:/container/path"}
    }),

    // 等待策略(使用WithExposedPorts时必须配置)
    // 为了可靠性,使用wait.ForListeningPort——绝对不要用time.Sleep!
    testcontainers.WithWaitStrategy(
        wait.ForListeningPort("80/tcp"),
        // 也可以使用其他策略:wait.ForLog(), wait.ForHTTP()等
    ),

    // 命令
    testcontainers.WithAfterReadyCommand(
        testcontainers.NewRawCommand([]string{"echo", "initialized"}),
    ),

    // 标签
    testcontainers.WithLabels(map[string]string{
        "app": "myapp",
    }),
)

4. Writing Integration Tests

4. 编写集成测试

Test Structure Best Practices

测试结构最佳实践

go
package myapp_test

import (
    "context"
    "testing"

    "github.com/stretchr/testify/require"
    "github.com/testcontainers/testcontainers-go"
    "github.com/testcontainers/testcontainers-go/modules/postgres"
)

func TestDatabaseOperations(t *testing.T) {
    // 1. Setup: Create context
    ctx := context.Background()

    // 2. Start container
    pgContainer, err := postgres.Run(ctx, "postgres:16-alpine")

    // 3. CRITICAL: Register cleanup BEFORE error check
    testcontainers.CleanupContainer(t, pgContainer)

    // 4. Check for errors
    require.NoError(t, err)

    // 5. Get connection details
    connStr, err := pgContainer.ConnectionString(ctx)
    require.NoError(t, err)

    // 6. Connect to service
    db, err := sql.Open("postgres", connStr)
    require.NoError(t, err)
    defer db.Close()

    // 7. Run your tests
    err = db.Ping()
    require.NoError(t, err)

    // Test your application logic here...
}
Critical pattern: Cleanup BEFORE error checking
go
// CORRECT:
ctr, err := testcontainers.Run(ctx, "nginx:alpine")
testcontainers.CleanupContainer(t, ctr)  // Register cleanup immediately
require.NoError(t, err)                   // Then check error

// WRONG: Creates resource leaks
ctr, err := testcontainers.Run(ctx, "nginx:alpine")
require.NoError(t, err)                   // If this fails...
testcontainers.CleanupContainer(t, ctr)  // ...cleanup never registers
go
package myapp_test

import (
    "context"
    "testing"

    "github.com/stretchr/testify/require"
    "github.com/testcontainers/testcontainers-go"
    "github.com/testcontainers/testcontainers-go/modules/postgres"
)

func TestDatabaseOperations(t *testing.T) {
    // 1. 初始化:创建上下文
    ctx := context.Background()

    // 2. 启动容器
    pgContainer, err := postgres.Run(ctx, "postgres:16-alpine")

    // 3. 关键:在错误检查前注册清理操作
    testcontainers.CleanupContainer(t, pgContainer)

    // 4. 检查错误
    require.NoError(t, err)

    // 5. 获取连接信息
    connStr, err := pgContainer.ConnectionString(ctx)
    require.NoError(t, err)

    // 6. 连接到服务
    db, err := sql.Open("postgres", connStr)
    require.NoError(t, err)
    defer db.Close()

    // 7. 运行测试
    err = db.Ping()
    require.NoError(t, err)

    // 在此处测试你的应用逻辑...
}
关键模式:在错误检查前注册清理操作
go
// 正确做法:
ctr, err := testcontainers.Run(ctx, "nginx:alpine")
testcontainers.CleanupContainer(t, ctr)  // 立即注册清理操作
require.NoError(t, err)                   // 然后检查错误

// 错误做法:会导致资源泄漏
ctr, err := testcontainers.Run(ctx, "nginx:alpine")
require.NoError(t, err)                   // 如果此处失败...
testcontainers.CleanupContainer(t, ctr)  // ...清理操作永远不会被注册

Table-Driven Tests with Containers

结合表格驱动测试使用容器

go
func TestMultipleVersions(t *testing.T) {
    ctx := context.Background()

    versions := []struct {
        name  string
        image string
    }{
        {"Postgres 14", "postgres:14-alpine"},
        {"Postgres 15", "postgres:15-alpine"},
        {"Postgres 16", "postgres:16-alpine"},
    }

    for _, tc := range versions {
        t.Run(tc.name, func(t *testing.T) {
            pgContainer, err := postgres.Run(ctx, tc.image)
            testcontainers.CleanupContainer(t, pgContainer)
            require.NoError(t, err)

            // Run tests against this version...
        })
    }
}
go
func TestMultipleVersions(t *testing.T) {
    ctx := context.Background()

    versions := []struct {
        name  string
        image string
    }{
        {"Postgres 14", "postgres:14-alpine"},
        {"Postgres 15", "postgres:15-alpine"},
        {"Postgres 16", "postgres:16-alpine"},
    }

    for _, tc := range versions {
        t.Run(tc.name, func(t *testing.T) {
            pgContainer, err := postgres.Run(ctx, tc.image)
            testcontainers.CleanupContainer(t, pgContainer)
            require.NoError(t, err)

            // 针对该版本运行测试...
        })
    }
}

Parallel Test Execution

并行测试执行

go
func TestParallelContainers(t *testing.T) {
    t.Parallel()  // Enable parallel execution

    ctx := context.Background()

    pgContainer, err := postgres.Run(ctx, "postgres:16-alpine")
    testcontainers.CleanupContainer(t, pgContainer)
    require.NoError(t, err)

    // Each parallel test gets its own container
}

go
func TestParallelContainers(t *testing.T) {
    t.Parallel()  // 启用并行执行

    ctx := context.Background()

    pgContainer, err := postgres.Run(ctx, "postgres:16-alpine")
    testcontainers.CleanupContainer(t, pgContainer)
    require.NoError(t, err)

    // 每个并行测试都会获得独立的容器
}

5. Container Networking

5. 容器网络

Connecting Multiple Containers

连接多个容器

go
import "github.com/testcontainers/testcontainers-go/network"

func TestMultipleServices(t *testing.T) {
    ctx := context.Background()

    // Create custom network
    nw, err := network.New(ctx)
    testcontainers.CleanupNetwork(t, nw)
    require.NoError(t, err)

    // Start database on network
    pgContainer, err := postgres.Run(
        ctx,
        "postgres:16-alpine",
        network.WithNetwork([]string{"database"}, nw),
    )
    testcontainers.CleanupContainer(t, pgContainer)
    require.NoError(t, err)

    // Start application on same network
    appContainer, err := testcontainers.Run(
        ctx,
        "myapp:latest",
        testcontainers.WithEnv(map[string]string{
            "DB_HOST": "database",  // Can reach via network alias
            "DB_PORT": "5432",      // Use internal port, not mapped port
        }),
        network.WithNetwork([]string{"app"}, nw),
    )
    testcontainers.CleanupContainer(t, appContainer)
    require.NoError(t, err)

    // Test application can communicate with database...
}
go
import "github.com/testcontainers/testcontainers-go/network"

func TestMultipleServices(t *testing.T) {
    ctx := context.Background()

    // 创建自定义网络
    nw, err := network.New(ctx)
    testcontainers.CleanupNetwork(t, nw)
    require.NoError(t, err)

    // 在网络上启动数据库
    pgContainer, err := postgres.Run(
        ctx,
        "postgres:16-alpine",
        network.WithNetwork([]string{"database"}, nw),
    )
    testcontainers.CleanupContainer(t, pgContainer)
    require.NoError(t, err)

    // 在同一网络上启动应用
    appContainer, err := testcontainers.Run(
        ctx,
        "myapp:latest",
        testcontainers.WithEnv(map[string]string{
            "DB_HOST": "database",  // 可通过网络别名访问
            "DB_PORT": "5432",      // 使用内部端口,而非映射端口
        }),
        network.WithNetwork([]string{"app"}, nw),
    )
    testcontainers.CleanupContainer(t, appContainer)
    require.NoError(t, err)

    // 测试应用能否与数据库通信...
}

Accessing Container Ports

访问容器端口

go
func TestPortAccess(t *testing.T) {
    ctx := context.Background()

    ctr, err := testcontainers.Run(
        ctx,
        "nginx:alpine",
        testcontainers.WithExposedPorts("80/tcp"),
    )
    testcontainers.CleanupContainer(t, ctr)
    require.NoError(t, err)

    // Method 1: Get full endpoint (recommended)
    endpoint, err := ctr.Endpoint(ctx, "http")
    require.NoError(t, err)
    // endpoint = "http://localhost:49153"

    // Method 2: Get mapped port only
    port, err := ctr.MappedPort(ctx, "80/tcp")
    require.NoError(t, err)
    portNum := port.Int()  // e.g., 49153

    // Method 3: Get host and port separately
    host, err := ctr.Host(ctx)
    require.NoError(t, err)
    // host = "localhost" (or docker host IP)
}

go
func TestPortAccess(t *testing.T) {
    ctx := context.Background()

    ctr, err := testcontainers.Run(
        ctx,
        "nginx:alpine",
        testcontainers.WithExposedPorts("80/tcp"),
    )
    testcontainers.CleanupContainer(t, ctr)
    require.NoError(t, err)

    // 方法1:获取完整端点(推荐)
    endpoint, err := ctr.Endpoint(ctx, "http")
    require.NoError(t, err)
    // endpoint = "http://localhost:49153"

    // 方法2:仅获取映射端口
    port, err := ctr.MappedPort(ctx, "80/tcp")
    require.NoError(t, err)
    portNum := port.Int()  // 例如:49153

    // 方法3:分别获取主机和端口
    host, err := ctr.Host(ctx)
    require.NoError(t, err)
    // host = "localhost"(或Docker主机IP)
}

6. Resource Management & Cleanup

6. 资源管理与清理

Cleanup Methods

清理方法

Method 1:
testcontainers.CleanupContainer()
(Recommended)
go
func TestRecommendedCleanup(t *testing.T) {
    ctx := context.Background()

    ctr, err := testcontainers.Run(ctx, "nginx:alpine")
    testcontainers.CleanupContainer(t, ctr)  // Registers with t.Cleanup
    require.NoError(t, err)

    // Container automatically cleaned up when test ends
}
Method 2:
t.Cleanup()
(Manual)
go
func TestManualCleanup(t *testing.T) {
    ctx := context.Background()

    ctr, err := testcontainers.Run(ctx, "nginx:alpine")
    require.NoError(t, err)

    t.Cleanup(func() {
        err := testcontainers.TerminateContainer(ctr)
        require.NoError(t, err)
    })
}
Method 3:
defer
(Legacy)
go
func TestDeferCleanup(t *testing.T) {
    ctx := context.Background()

    ctr, err := testcontainers.Run(ctx, "nginx:alpine")
    require.NoError(t, err)

    defer func() {
        err := testcontainers.TerminateContainer(ctr)
        require.NoError(t, err)
    }()
}
方法1:
testcontainers.CleanupContainer()
(推荐)
go
func TestRecommendedCleanup(t *testing.T) {
    ctx := context.Background()

    ctr, err := testcontainers.Run(ctx, "nginx:alpine")
    testcontainers.CleanupContainer(t, ctr)  // 注册到t.Cleanup
    require.NoError(t, err)

    // 测试结束时容器将自动被清理
}
方法2:
t.Cleanup()
(手动)
go
func TestManualCleanup(t *testing.T) {
    ctx := context.Background()

    ctr, err := testcontainers.Run(ctx, "nginx:alpine")
    require.NoError(t, err)

    t.Cleanup(func() {
        err := testcontainers.TerminateContainer(ctr)
        require.NoError(t, err)
    })
}
方法3:
defer
(传统方式)
go
func TestDeferCleanup(t *testing.T) {
    ctx := context.Background()

    ctr, err := testcontainers.Run(ctx, "nginx:alpine")
    require.NoError(t, err)

    defer func() {
        err := testcontainers.TerminateContainer(ctr)
        require.NoError(t, err)
    }()
}

Cleanup Options

清理选项

go
// Cleanup with custom timeout
testcontainers.CleanupContainer(t, ctr,
    testcontainers.StopTimeout(10*time.Second),
)

// Cleanup and remove volumes
testcontainers.CleanupContainer(t, ctr,
    testcontainers.RemoveVolumes("volume1", "volume2"),
)

// Combine options
testcontainers.CleanupContainer(t, ctr,
    testcontainers.StopTimeout(5*time.Second),
    testcontainers.RemoveVolumes("data"),
)
go
// 带自定义超时的清理
testcontainers.CleanupContainer(t, ctr,
    testcontainers.StopTimeout(10*time.Second),
)

// 清理并删除卷
testcontainers.CleanupContainer(t, ctr,
    testcontainers.RemoveVolumes("volume1", "volume2"),
)

// 组合选项
testcontainers.CleanupContainer(t, ctr,
    testcontainers.StopTimeout(5*time.Second),
    testcontainers.RemoveVolumes("data"),
)

Automatic Cleanup with Ryuk

使用Ryuk自动清理

Testcontainers for Go uses Ryuk, a garbage collector that automatically cleans up containers even if tests crash or timeout:
  • Runs as a sidecar container (
    testcontainers/ryuk:0.13.0
    )
  • Monitors test session lifecycle
  • Cleans up containers when session ends
  • Handles parallel test execution
Control Ryuk behavior:
go
// Disable Ryuk (not recommended)
os.Setenv("TESTCONTAINERS_RYUK_DISABLED", "true")

// Enable verbose logging
os.Setenv("RYUK_VERBOSE", "true")

// Adjust timeouts
os.Setenv("RYUK_CONNECTION_TIMEOUT", "2m")
os.Setenv("RYUK_RECONNECTION_TIMEOUT", "30s")

Testcontainers for Go使用Ryuk(垃圾回收器),即使测试崩溃或超时,它也能自动清理容器:
  • 作为边车容器运行(
    testcontainers/ryuk:0.13.0
  • 监控测试会话生命周期
  • 会话结束时清理容器
  • 支持并行测试执行
控制Ryuk行为:
go
// 禁用Ryuk(不推荐)
os.Setenv("TESTCONTAINERS_RYUK_DISABLED", "true")

// 启用详细日志
os.Setenv("RYUK_VERBOSE", "true")

// 调整超时
os.Setenv("RYUK_CONNECTION_TIMEOUT", "2m")
os.Setenv("RYUK_RECONNECTION_TIMEOUT", "30s")

7. Configuration Patterns

7. 配置模式

Environment Variables

环境变量

go
testcontainers.Run(
    ctx,
    "myapp:latest",
    testcontainers.WithEnv(map[string]string{
        "DATABASE_URL": "postgres://localhost/db",
        "LOG_LEVEL":    "debug",
        "API_KEY":      "test-key",
    }),
)
go
testcontainers.Run(
    ctx,
    "myapp:latest",
    testcontainers.WithEnv(map[string]string{
        "DATABASE_URL": "postgres://localhost/db",
        "LOG_LEVEL":    "debug",
        "API_KEY":      "test-key",
    }),
)

Executing Commands in Containers

在容器中执行命令

When executing commands with
Exec()
, it's recommended to use
exec.Multiplexed()
to properly handle Docker's output format:
go
import "github.com/testcontainers/testcontainers-go/exec"

// Execute command with Multiplexed option
exitCode, reader, err := ctr.Exec(ctx, []string{"sh", "-c", "echo 'hello'"}, exec.Multiplexed())
require.NoError(t, err)
require.Equal(t, 0, exitCode)

// Read the output
output, err := io.ReadAll(reader)
require.NoError(t, err)
fmt.Println(string(output))
Why use
exec.Multiplexed()
?
  • Removes Docker's multiplexing headers from the output
  • Combines stdout and stderr into a single clean stream
  • Makes the output easier to read and parse
Without
exec.Multiplexed()
, you'll get Docker's raw multiplexed stream which includes header bytes that are difficult to parse.
使用
Exec()
执行命令时,推荐使用
exec.Multiplexed()
以正确处理Docker的输出格式:
go
import "github.com/testcontainers/testcontainers-go/exec"

// 使用Multiplexed选项执行命令
exitCode, reader, err := ctr.Exec(ctx, []string{"sh", "-c", "echo 'hello'"}, exec.Multiplexed())
require.NoError(t, err)
require.Equal(t, 0, exitCode)

// 读取输出
output, err := io.ReadAll(reader)
require.NoError(t, err)
fmt.Println(string(output))
为什么使用
exec.Multiplexed()
  • 从输出中移除Docker的多路复用头
  • 将stdout和stderr合并为一个干净的流
  • 使输出更易于读取和解析
如果不使用
exec.Multiplexed()
,你将得到Docker的原始多路复用流,其中包含难以解析的头字节。

Files and Directories

文件与目录

go
// Copy single file
testcontainers.Run(
    ctx,
    "nginx:alpine",
    testcontainers.WithFiles(testcontainers.ContainerFile{
        Reader:            strings.NewReader("server { listen 80; }"),
        ContainerFilePath: "/etc/nginx/conf.d/default.conf",
        FileMode:          0o644,
    }),
)

// Copy multiple files
testcontainers.Run(
    ctx,
    "myapp:latest",
    testcontainers.WithFiles(
        testcontainers.ContainerFile{...},  // config.yml
        testcontainers.ContainerFile{...},  // secrets.json
    ),
)

// Copy from container after start
ctr, _ := testcontainers.Run(ctx, "nginx:alpine")
reader, err := ctr.CopyFileFromContainer(ctx, "/etc/nginx/nginx.conf")
content, _ := io.ReadAll(reader)
go
// 复制单个文件
testcontainers.Run(
    ctx,
    "nginx:alpine",
    testcontainers.WithFiles(testcontainers.ContainerFile{
        Reader:            strings.NewReader("server { listen 80; }"),
        ContainerFilePath: "/etc/nginx/conf.d/default.conf",
        FileMode:          0o644,
    }),
)

// 复制多个文件
testcontainers.Run(
    ctx,
    "myapp:latest",
    testcontainers.WithFiles(
        testcontainers.ContainerFile{...},  // config.yml
        testcontainers.ContainerFile{...},  // secrets.json
    ),
)

// 容器启动后从容器中复制文件
ctr, _ := testcontainers.Run(ctx, "nginx:alpine")
reader, err := ctr.CopyFileFromContainer(ctx, "/etc/nginx/nginx.conf")
content, _ := io.ReadAll(reader)

Volume Mounts

卷挂载

go
testcontainers.Run(
    ctx,
    "postgres:16",
    testcontainers.WithHostConfigModifier(func(hc *container.HostConfig) {
        // Bind mount
        hc.Binds = []string{
            "/host/data:/var/lib/postgresql/data",
        }

        // Named volume
        hc.Mounts = []mount.Mount{
            {
                Type:   mount.TypeVolume,
                Source: "pgdata",
                Target: "/var/lib/postgresql/data",
            },
        }
    }),
)
go
testcontainers.Run(
    ctx,
    "postgres:16",
    testcontainers.WithHostConfigModifier(func(hc *container.HostConfig) {
        // 绑定挂载
        hc.Binds = []string{
            "/host/data:/var/lib/postgresql/data",
        }

        // 命名卷
        hc.Mounts = []mount.Mount{
            {
                Type:   mount.TypeVolume,
                Source: "pgdata",
                Target: "/var/lib/postgresql/data",
            },
        }
    }),
)

Temporary Filesystems

临时文件系统

go
testcontainers.Run(
    ctx,
    "myapp:latest",
    testcontainers.WithTmpfs(map[string]string{
        "/tmp":      "rw",
        "/app/temp": "rw,size=100m,mode=1777",
    }),
)

go
testcontainers.Run(
    ctx,
    "myapp:latest",
    testcontainers.WithTmpfs(map[string]string{
        "/tmp":      "rw",
        "/app/temp": "rw,size=100m,mode=1777",
    }),
)

8. Wait Strategies

8. 等待策略

Wait strategies are critical for reliable tests. They ensure containers are fully ready before tests run, which is especially important in CI environments where timing can vary.
Best Practices:
  • Always use
    wait.ForListeningPort()
    when exposing ports
    - This is the most reliable approach
  • Choose appropriate wait strategies based on your service (HTTP health checks, log patterns, etc.)
  • Never use
    time.Sleep()
    - This is an anti-pattern that leads to flaky tests
  • Set reasonable timeouts to handle slow CI environments
等待策略对于测试可靠性至关重要。它们确保容器在测试运行前完全就绪,这在CI环境中尤为重要,因为CI环境的时序可能会有变化。
最佳实践:
  • 暴露端口时始终使用
    wait.ForListeningPort()
    - 这是最可靠的方法
  • 根据服务选择合适的等待策略(HTTP健康检查、日志模式等)
  • 绝对不要使用
    time.Sleep()
    - 这是一种会导致测试不稳定的反模式
  • 设置合理的超时时间以适配慢速CI环境

Port-Based Waiting (Recommended for Exposed Ports)

基于端口的等待(暴露端口时推荐)

go
import "github.com/testcontainers/testcontainers-go/wait"

testcontainers.Run(
    ctx,
    "postgres:16",
    testcontainers.WithWaitStrategy(
        wait.ForListeningPort("5432/tcp").
            WithStartupTimeout(30*time.Second).
            WithPollInterval(1*time.Second),
    ),
)
go
import "github.com/testcontainers/testcontainers-go/wait"

testcontainers.Run(
    ctx,
    "postgres:16",
    testcontainers.WithWaitStrategy(
        wait.ForListeningPort("5432/tcp").
            WithStartupTimeout(30*time.Second).
            WithPollInterval(1*time.Second),
    ),
)

Log-Based Waiting

基于日志的等待

go
testcontainers.Run(
    ctx,
    "elasticsearch:8.7.0",
    testcontainers.WithWaitStrategy(
        wait.ForLog("started").
            WithStartupTimeout(60*time.Second).
            WithOccurrence(1),
    ),
)
go
testcontainers.Run(
    ctx,
    "elasticsearch:8.7.0",
    testcontainers.WithWaitStrategy(
        wait.ForLog("started").
            WithStartupTimeout(60*time.Second).
            WithOccurrence(1),
    ),
)

HTTP-Based Waiting

基于HTTP的等待

go
testcontainers.Run(
    ctx,
    "myapp:latest",
    testcontainers.WithWaitStrategy(
        wait.ForHTTP("/health").
            WithPort("8080/tcp").
            WithStatusCodeMatcher(func(status int) bool {
                return status == 200
            }).
            WithStartupTimeout(30*time.Second),
    ),
)
go
testcontainers.Run(
    ctx,
    "myapp:latest",
    testcontainers.WithWaitStrategy(
        wait.ForHTTP("/health").
            WithPort("8080/tcp").
            WithStatusCodeMatcher(func(status int) bool {
                return status == 200
            }).
            WithStartupTimeout(30*time.Second),
    ),
)

SQL-Based Waiting

基于SQL的等待

go
testcontainers.Run(
    ctx,
    "postgres:16",
    testcontainers.WithWaitStrategy(
        wait.ForSQL("5432/tcp", "postgres", func(host string, port nat.Port) string {
            return fmt.Sprintf("postgres://user:pass@%s:%s/db?sslmode=disable",
                host, port.Port())
        }).WithStartupTimeout(30*time.Second),
    ),
)
go
testcontainers.Run(
    ctx,
    "postgres:16",
    testcontainers.WithWaitStrategy(
        wait.ForSQL("5432/tcp", "postgres", func(host string, port nat.Port) string {
            return fmt.Sprintf("postgres://user:pass@%s:%s/db?sslmode=disable",
                host, port.Port())
        }).WithStartupTimeout(30*time.Second),
    ),
)

Multiple Wait Strategies

多等待策略组合

go
testcontainers.Run(
    ctx,
    "myapp:latest",
    testcontainers.WithWaitStrategy(
        wait.ForAll(
            wait.ForListeningPort("8080/tcp"),
            wait.ForLog("Application started"),
            wait.ForHTTP("/health"),
        ),
    ),
)

go
testcontainers.Run(
    ctx,
    "myapp:latest",
    testcontainers.WithWaitStrategy(
        wait.ForAll(
            wait.ForListeningPort("8080/tcp"),
            wait.ForLog("Application started"),
            wait.ForHTTP("/health"),
        ),
    ),
)

9. Troubleshooting

9. 故障排查

Check Docker Availability

检查Docker可用性

go
func TestDockerConnection(t *testing.T) {
    testcontainers.SkipIfProviderIsNotHealthy(t)

    ctx := context.Background()
    cli, err := testcontainers.NewDockerClientWithOpts(ctx)
    require.NoError(t, err)

    info, err := cli.Info(ctx)
    require.NoError(t, err)

    t.Logf("Docker version: %s", info.ServerVersion)
    t.Logf("OS: %s", info.OperatingSystem)
}
go
func TestDockerConnection(t *testing.T) {
    testcontainers.SkipIfProviderIsNotHealthy(t)

    ctx := context.Background()
    cli, err := testcontainers.NewDockerClientWithOpts(ctx)
    require.NoError(t, err)

    info, err := cli.Info(ctx)
    require.NoError(t, err)

    t.Logf("Docker版本: %s", info.ServerVersion)
    t.Logf("操作系统: %s", info.OperatingSystem)
}

Debug Container Logs

调试容器日志

go
func TestWithLogging(t *testing.T) {
    ctx := context.Background()

    // Method 1: Stream to stdout
    ctr, _ := testcontainers.Run(
        ctx,
        "myapp:latest",
        testcontainers.WithLogConsumers(
            &testcontainers.StdoutLogConsumer{},
        ),
    )
    testcontainers.CleanupContainer(t, ctr)

    // Method 2: Read logs manually
    rc, _ := ctr.Logs(ctx)
    defer rc.Close()
    logs, _ := io.ReadAll(rc)
    t.Logf("Container logs:\n%s", string(logs))

    // Method 3: Inspect container
    info, _ := ctr.Inspect(ctx)
    t.Logf("Container state: %+v", info.State)
}
go
func TestWithLogging(t *testing.T) {
    ctx := context.Background()

    // 方法1:将日志流输出到标准输出
    ctr, _ := testcontainers.Run(
        ctx,
        "myapp:latest",
        testcontainers.WithLogConsumers(
            &testcontainers.StdoutLogConsumer{},
        ),
    )
    testcontainers.CleanupContainer(t, ctr)

    // 方法2:手动读取日志
    rc, _ := ctr.Logs(ctx)
    defer rc.Close()
    logs, _ := io.ReadAll(rc)
    t.Logf("容器日志:\n%s", string(logs))

    // 方法3:检查容器详情
    info, _ := ctr.Inspect(ctx)
    t.Logf("容器状态: %+v", info.State)
}

Common Issues

常见问题

Issue: Container startup timeout
go
// Increase wait timeout
testcontainers.WithWaitStrategy(
    wait.ForListeningPort("5432/tcp").
        WithStartupTimeout(60*time.Second),  // Increase from default
)

// Check logs to see what's happening
testcontainers.WithLogConsumers(&testcontainers.StdoutLogConsumer{})
Issue: Port already in use
  • Testcontainers auto-assigns random ports
  • Don't manually specify host ports unless necessary
  • Check for leaked containers:
    docker ps -a
Issue: Image pull failures
bash
undefined
问题:容器启动超时
go
// 增加等待超时时间
testcontainers.WithWaitStrategy(
    wait.ForListeningPort("5432/tcp").
        WithStartupTimeout(60*time.Second),  // 从默认值增加
)

// 查看日志了解具体情况
testcontainers.WithLogConsumers(&testcontainers.StdoutLogConsumer{})
问题:端口已被占用
  • Testcontainers会自动分配随机端口
  • 除非必要,否则不要手动指定主机端口
  • 检查是否有泄漏的容器:
    docker ps -a
问题:镜像拉取失败
bash
// 先手动拉取镜像以验证
docker pull postgres:16

// 对于私有镜像仓库,先登录
docker login registry.example.com
// Testcontainers会使用~/.docker/config.json中的凭证
问题:容器未被清理
go
// 验证Ryuk是否在运行
docker ps | grep ryuk

// 检查清理操作是否正确注册
testcontainers.CleanupContainer(t, ctr)  // 在错误检查前执行!

Pull manually first to verify

用于调试的环境变量

docker pull postgres:16
bash
// 启用Ryuk详细日志
export RYUK_VERBOSE=true

// 调整超时时间
export RYUK_CONNECTION_TIMEOUT=2m
export RYUK_RECONNECTION_TIMEOUT=30s

// 自定义Docker套接字
export DOCKER_HOST=unix:///var/run/docker.sock

// 私有镜像仓库的前缀
export TESTCONTAINERS_HUB_IMAGE_NAME_PREFIX=private.registry.com

For private registries, login first

示例

示例1:PostgreSQL集成测试

docker login registry.example.com
go
package myapp_test

import (
    "context"
    "database/sql"
    "testing"

    _ "github.com/lib/pq"
    "github.com/stretchr/testify/require"
    "github.com/testcontainers/testcontainers-go"
    "github.com/testcontainers/testcontainers-go/modules/postgres"
)

func TestUserRepository(t *testing.T) {
    ctx := context.Background()

    // 启动PostgreSQL容器
    pgContainer, err := postgres.Run(
        ctx,
        "postgres:16-alpine",
        postgres.WithDatabase("testdb"),
        postgres.WithUsername("testuser"),
        postgres.WithPassword("testpass"),
        postgres.WithInitScripts("./testdata/schema.sql"),
    )
    testcontainers.CleanupContainer(t, pgContainer)
    require.NoError(t, err)

    // 获取连接字符串
    connStr, err := pgContainer.ConnectionString(ctx, "sslmode=disable")
    require.NoError(t, err)

    // 连接到数据库
    db, err := sql.Open("postgres", connStr)
    require.NoError(t, err)
    defer db.Close()

    // 测试你的仓库
    repo := NewUserRepository(db)

    t.Run("CreateUser", func(t *testing.T) {
        user := &User{Name: "Alice", Email: "alice@example.com"}
        err := repo.Create(user)
        require.NoError(t, err)
        require.NotZero(t, user.ID)
    })

    t.Run("GetUser", func(t *testing.T) {
        user, err := repo.GetByEmail("alice@example.com")
        require.NoError(t, err)
        require.Equal(t, "Alice", user.Name)
    })
}

Testcontainers will use credentials from ~/.docker/config.json

示例2:Redis缓存测试


**Issue: Container not cleaning up**
```go
// Verify Ryuk is running
docker ps | grep ryuk

// Check cleanup is registered correctly
testcontainers.CleanupContainer(t, ctr)  // Before error check!
go
package cache_test

import (
    "context"
    "testing"
    "time"

    "github.com/redis/go-redis/v9"
    "github.com/stretchr/testify/require"
    "github.com/testcontainers/testcontainers-go"
    "github.com/testcontainers/testcontainers-go/modules/redis"
)

func TestRedisCache(t *testing.T) {
    ctx := context.Background()

    // 启动Redis容器
    redisContainer, err := redis.Run(
        ctx,
        "redis:7-alpine",
        redis.WithSnapshotting(10, 1),
        redis.WithLogLevel(redis.LogLevelVerbose),
    )
    testcontainers.CleanupContainer(t, redisContainer)
    require.NoError(t, err)

    // 获取连接字符串
    connStr, err := redisContainer.ConnectionString(ctx)
    require.NoError(t, err)

    // 连接到Redis
    opt, err := redis.ParseURL(connStr)
    require.NoError(t, err)

    client := redis.NewClient(opt)
    defer client.Close()

    // 测试缓存操作
    t.Run("SetAndGet", func(t *testing.T) {
        err := client.Set(ctx, "key1", "value1", time.Minute).Err()
        require.NoError(t, err)

        val, err := client.Get(ctx, "key1").Result()
        require.NoError(t, err)
        require.Equal(t, "value1", val)
    })

    t.Run("Expiration", func(t *testing.T) {
        err := client.Set(ctx, "key2", "value2", time.Second).Err()
        require.NoError(t, err)

        time.Sleep(2 * time.Second)

        _, err = client.Get(ctx, "key2").Result()
        require.Equal(t, redis.Nil, err)
    })
}

Environment Variables for Debugging

示例3:Kafka生产者/消费者测试

bash
undefined
go
package messaging_test

import (
    "context"
    "testing"
    "time"

    "github.com/segmentio/kafka-go"
    "github.com/stretchr/testify/require"
    "github.com/testcontainers/testcontainers-go"
    "github.com/testcontainers/testcontainers-go/modules/kafka"
)

func TestKafkaMessaging(t *testing.T) {
    ctx := context.Background()

    // 启动Kafka容器
    kafkaContainer, err := kafka.Run(
        ctx,
        "confluentinc/confluent-local:7.5.0",
        kafka.WithClusterID("test-cluster"),
    )
    testcontainers.CleanupContainer(t, kafkaContainer)
    require.NoError(t, err)

    // 获取引导服务器
    brokers, err := kafkaContainer.Brokers(ctx)
    require.NoError(t, err)

    topic := "test-topic"

    // 创建生产者
    writer := kafka.NewWriter(kafka.WriterConfig{
        Brokers: brokers,
        Topic:   topic,
    })
    defer writer.Close()

    // 创建消费者
    reader := kafka.NewReader(kafka.ReaderConfig{
        Brokers: brokers,
        Topic:   topic,
        GroupID: "test-group",
    })
    defer reader.Close()

    // 测试消息流
    t.Run("ProduceAndConsume", func(t *testing.T) {
        // 生产消息
        err := writer.WriteMessages(ctx, kafka.Message{
            Key:   []byte("key1"),
            Value: []byte("Hello, Kafka!"),
        })
        require.NoError(t, err)

        // 消费消息
        msg, err := reader.ReadMessage(ctx)
        require.NoError(t, err)
        require.Equal(t, "Hello, Kafka!", string(msg.Value))
    })
}

Enable Ryuk verbose logging

示例4:多容器应用栈

export RYUK_VERBOSE=true
go
package integration_test

import (
    "context"
    "net/http"
    "testing"

    "github.com/stretchr/testify/require"
    "github.com/testcontainers/testcontainers-go"
    "github.com/testcontainers/testcontainers-go/modules/postgres"
    "github.com/testcontainers/testcontainers-go/modules/redis"
    "github.com/testcontainers/testcontainers-go/network"
)

func TestFullStack(t *testing.T) {
    ctx := context.Background()

    // 创建自定义网络
    nw, err := network.New(ctx)
    testcontainers.CleanupNetwork(t, nw)
    require.NoError(t, err)

    // 启动PostgreSQL
    pgContainer, err := postgres.Run(
        ctx,
        "postgres:16-alpine",
        network.WithNetwork([]string{"database"}, nw),
    )
    testcontainers.CleanupContainer(t, pgContainer)
    require.NoError(t, err)

    // 启动Redis
    redisContainer, err := redis.Run(
        ctx,
        "redis:7-alpine",
        network.WithNetwork([]string{"cache"}, nw),
    )
    testcontainers.CleanupContainer(t, redisContainer)
    require.NoError(t, err)

    // 启动应用
    appContainer, err := testcontainers.Run(
        ctx,
        "myapp:latest",
        testcontainers.WithEnv(map[string]string{
            "DB_HOST":    "database",
            "DB_PORT":    "5432",
            "REDIS_HOST": "cache",
            "REDIS_PORT": "6379",
        }),
        testcontainers.WithExposedPorts("8080/tcp"),
        network.WithNetwork([]string{"app"}, nw),
    )
    testcontainers.CleanupContainer(t, appContainer)
    require.NoError(t, err)

    // 获取应用端点
    endpoint, err := appContainer.Endpoint(ctx, "http")
    require.NoError(t, err)

    // 测试应用
    resp, err := http.Get(endpoint + "/health")
    require.NoError(t, err)
    require.Equal(t, 200, resp.StatusCode)
}

Adjust timeouts

示例5:Docker Compose应用栈

export RYUK_CONNECTION_TIMEOUT=2m export RYUK_RECONNECTION_TIMEOUT=30s
go
package compose_test

import (
    "context"
    "testing"

    "github.com/stretchr/testify/require"
    "github.com/testcontainers/testcontainers-go"
    "github.com/testcontainers/testcontainers-go/modules/compose"
)

func TestComposeStack(t *testing.T) {
    ctx := context.Background()

    // 从docker-compose.yml启动服务
    composeStack, err := compose.NewDockerCompose("./docker-compose.yml")
    require.NoError(t, err)

    t.Cleanup(func() {
        if err := composeStack.Down(ctx); err != nil {
            t.Fatalf("关闭compose应用栈失败: %v", err)
        }
    })

    err = composeStack.Up(ctx, compose.Wait(true))
    require.NoError(t, err)

    // 获取服务容器
    webContainer, err := composeStack.ServiceContainer(ctx, "web")
    require.NoError(t, err)

    // 测试服务
    endpoint, err := webContainer.Endpoint(ctx, "http")
    require.NoError(t, err)

    // 针对应用栈运行测试...
}

Custom Docker socket

最佳实践

export DOCKER_HOST=unix:///var/run/docker.sock
  1. 只要有可用模块就优先使用预配置模块 - 它们提供合理的默认值和辅助方法
  2. 立即注册清理操作 - 在检查错误前调用
    testcontainers.CleanupContainer(t, ctr)
  3. 暴露端口时务必添加等待策略 - 使用
    wait.ForListeningPort()
    确保可靠性,尤其是在CI环境中。绝对不要使用
    time.Sleep()
    - 这是一种会导致测试不稳定的反模式
  4. 选择合适的等待策略 - 对健康端点使用
    wait.ForHTTP()
    ,对日志模式使用
    wait.ForLog()
    ,或对端口可用性使用
    wait.ForListeningPort()
  5. 利用表格驱动测试 - 针对多个版本或配置进行测试
  6. 使用自定义网络 - 用于多容器通信
  7. 保持容器的临时性 - 不要依赖测试间的状态
  8. 检查Docker可用性 - 使用
    testcontainers.SkipIfProviderIsNotHealthy(t)
  9. 启用并行执行 - 使用
    t.Parallel()
    加快测试套件的运行速度
  10. 使用模块辅助方法 - 例如
    ConnectionString()
    Snapshot()
    Restore()
  11. 通过日志调试 - 排查问题时使用
    WithLogConsumers()

Registry prefix for private registry

额外资源

export TESTCONTAINERS_HUB_IMAGE_NAME_PREFIX=private.registry.com

---

Examples

Example 1: PostgreSQL Integration Test

go
package myapp_test

import (
    "context"
    "database/sql"
    "testing"

    _ "github.com/lib/pq"
    "github.com/stretchr/testify/require"
    "github.com/testcontainers/testcontainers-go"
    "github.com/testcontainers/testcontainers-go/modules/postgres"
)

func TestUserRepository(t *testing.T) {
    ctx := context.Background()

    // Start PostgreSQL container
    pgContainer, err := postgres.Run(
        ctx,
        "postgres:16-alpine",
        postgres.WithDatabase("testdb"),
        postgres.WithUsername("testuser"),
        postgres.WithPassword("testpass"),
        postgres.WithInitScripts("./testdata/schema.sql"),
    )
    testcontainers.CleanupContainer(t, pgContainer)
    require.NoError(t, err)

    // Get connection string
    connStr, err := pgContainer.ConnectionString(ctx, "sslmode=disable")
    require.NoError(t, err)

    // Connect to database
    db, err := sql.Open("postgres", connStr)
    require.NoError(t, err)
    defer db.Close()

    // Test your repository
    repo := NewUserRepository(db)

    t.Run("CreateUser", func(t *testing.T) {
        user := &User{Name: "Alice", Email: "alice@example.com"}
        err := repo.Create(user)
        require.NoError(t, err)
        require.NotZero(t, user.ID)
    })

    t.Run("GetUser", func(t *testing.T) {
        user, err := repo.GetByEmail("alice@example.com")
        require.NoError(t, err)
        require.Equal(t, "Alice", user.Name)
    })
}

Example 2: Redis Cache Test

go
package cache_test

import (
    "context"
    "testing"
    "time"

    "github.com/redis/go-redis/v9"
    "github.com/stretchr/testify/require"
    "github.com/testcontainers/testcontainers-go"
    "github.com/testcontainers/testcontainers-go/modules/redis"
)

func TestRedisCache(t *testing.T) {
    ctx := context.Background()

    // Start Redis container
    redisContainer, err := redis.Run(
        ctx,
        "redis:7-alpine",
        redis.WithSnapshotting(10, 1),
        redis.WithLogLevel(redis.LogLevelVerbose),
    )
    testcontainers.CleanupContainer(t, redisContainer)
    require.NoError(t, err)

    // Get connection string
    connStr, err := redisContainer.ConnectionString(ctx)
    require.NoError(t, err)

    // Connect to Redis
    opt, err := redis.ParseURL(connStr)
    require.NoError(t, err)

    client := redis.NewClient(opt)
    defer client.Close()

    // Test cache operations
    t.Run("SetAndGet", func(t *testing.T) {
        err := client.Set(ctx, "key1", "value1", time.Minute).Err()
        require.NoError(t, err)

        val, err := client.Get(ctx, "key1").Result()
        require.NoError(t, err)
        require.Equal(t, "value1", val)
    })

    t.Run("Expiration", func(t *testing.T) {
        err := client.Set(ctx, "key2", "value2", time.Second).Err()
        require.NoError(t, err)

        time.Sleep(2 * time.Second)

        _, err = client.Get(ctx, "key2").Result()
        require.Equal(t, redis.Nil, err)
    })
}

Example 3: Kafka Producer/Consumer Test

go
package messaging_test

import (
    "context"
    "testing"
    "time"

    "github.com/segmentio/kafka-go"
    "github.com/stretchr/testify/require"
    "github.com/testcontainers/testcontainers-go"
    "github.com/testcontainers/testcontainers-go/modules/kafka"
)

func TestKafkaMessaging(t *testing.T) {
    ctx := context.Background()

    // Start Kafka container
    kafkaContainer, err := kafka.Run(
        ctx,
        "confluentinc/confluent-local:7.5.0",
        kafka.WithClusterID("test-cluster"),
    )
    testcontainers.CleanupContainer(t, kafkaContainer)
    require.NoError(t, err)

    // Get bootstrap servers
    brokers, err := kafkaContainer.Brokers(ctx)
    require.NoError(t, err)

    topic := "test-topic"

    // Create producer
    writer := kafka.NewWriter(kafka.WriterConfig{
        Brokers: brokers,
        Topic:   topic,
    })
    defer writer.Close()

    // Create consumer
    reader := kafka.NewReader(kafka.ReaderConfig{
        Brokers: brokers,
        Topic:   topic,
        GroupID: "test-group",
    })
    defer reader.Close()

    // Test message flow
    t.Run("ProduceAndConsume", func(t *testing.T) {
        // Produce message
        err := writer.WriteMessages(ctx, kafka.Message{
            Key:   []byte("key1"),
            Value: []byte("Hello, Kafka!"),
        })
        require.NoError(t, err)

        // Consume message
        msg, err := reader.ReadMessage(ctx)
        require.NoError(t, err)
        require.Equal(t, "Hello, Kafka!", string(msg.Value))
    })
}

Example 4: Multi-Container Application Stack

go
package integration_test

import (
    "context"
    "net/http"
    "testing"

    "github.com/stretchr/testify/require"
    "github.com/testcontainers/testcontainers-go"
    "github.com/testcontainers/testcontainers-go/modules/postgres"
    "github.com/testcontainers/testcontainers-go/modules/redis"
    "github.com/testcontainers/testcontainers-go/network"
)

func TestFullStack(t *testing.T) {
    ctx := context.Background()

    // Create custom network
    nw, err := network.New(ctx)
    testcontainers.CleanupNetwork(t, nw)
    require.NoError(t, err)

    // Start PostgreSQL
    pgContainer, err := postgres.Run(
        ctx,
        "postgres:16-alpine",
        network.WithNetwork([]string{"database"}, nw),
    )
    testcontainers.CleanupContainer(t, pgContainer)
    require.NoError(t, err)

    // Start Redis
    redisContainer, err := redis.Run(
        ctx,
        "redis:7-alpine",
        network.WithNetwork([]string{"cache"}, nw),
    )
    testcontainers.CleanupContainer(t, redisContainer)
    require.NoError(t, err)

    // Start application
    appContainer, err := testcontainers.Run(
        ctx,
        "myapp:latest",
        testcontainers.WithEnv(map[string]string{
            "DB_HOST":    "database",
            "DB_PORT":    "5432",
            "REDIS_HOST": "cache",
            "REDIS_PORT": "6379",
        }),
        testcontainers.WithExposedPorts("8080/tcp"),
        network.WithNetwork([]string{"app"}, nw),
    )
    testcontainers.CleanupContainer(t, appContainer)
    require.NoError(t, err)

    // Get application endpoint
    endpoint, err := appContainer.Endpoint(ctx, "http")
    require.NoError(t, err)

    // Test application
    resp, err := http.Get(endpoint + "/health")
    require.NoError(t, err)
    require.Equal(t, 200, resp.StatusCode)
}

Example 5: Docker Compose Stack

go
package compose_test

import (
    "context"
    "testing"

    "github.com/stretchr/testify/require"
    "github.com/testcontainers/testcontainers-go"
    "github.com/testcontainers/testcontainers-go/modules/compose"
)

func TestComposeStack(t *testing.T) {
    ctx := context.Background()

    // Start services from docker-compose.yml
    composeStack, err := compose.NewDockerCompose("./docker-compose.yml")
    require.NoError(t, err)

    t.Cleanup(func() {
        if err := composeStack.Down(ctx); err != nil {
            t.Fatalf("failed to down compose stack: %v", err)
        }
    })

    err = composeStack.Up(ctx, compose.Wait(true))
    require.NoError(t, err)

    // Get service container
    webContainer, err := composeStack.ServiceContainer(ctx, "web")
    require.NoError(t, err)

    // Test service
    endpoint, err := webContainer.Endpoint(ctx, "http")
    require.NoError(t, err)

    // Run tests against the stack...
}

Best Practices

  1. Always use pre-configured modules when available - They provide sensible defaults and helper methods
  2. Register cleanup immediately - Call
    testcontainers.CleanupContainer(t, ctr)
    before checking errors
  3. Always add wait strategies when exposing ports - Use
    wait.ForListeningPort()
    to ensure reliability, especially in CI. Never use
    time.Sleep()
    - it's an anti-pattern that causes flaky tests
  4. Choose appropriate wait strategies - Use
    wait.ForHTTP()
    for health endpoints,
    wait.ForLog()
    for log patterns, or
    wait.ForListeningPort()
    for port availability
  5. Leverage table-driven tests - Test against multiple versions or configurations
  6. Use custom networks - For multi-container communication
  7. Keep containers ephemeral - Don't rely on state between tests
  8. Check Docker availability - Use
    testcontainers.SkipIfProviderIsNotHealthy(t)
  9. Enable parallel execution - Use
    t.Parallel()
    for faster test suites
  10. Use module helper methods - E.g.,
    ConnectionString()
    ,
    Snapshot()
    ,
    Restore()
  11. Debug with logs - Use
    WithLogConsumers()
    when troubleshooting

Additional Resources