Getting started with Testcontainers for Go


目录

In this guide, you will learn how to:

Prerequisites

[!NOTE] If you're new to Testcontainers, visit the Testcontainers overview to learn more about Testcontainers and the benefits of using it.

Create the Go project

Initialize the project

Start by creating a Go project.

$ mkdir testcontainers-go-demo
$ cd testcontainers-go-demo
$ go mod init github.com/testcontainers/testcontainers-go-demo

This guide uses the jackc/pgx PostgreSQL driver to interact with the Postgres database and the testcontainers-go Postgres module to spin up a Postgres Docker instance for testing. It also uses testify for running multiple tests as a suite and for writing assertions.

Install these dependencies:

$ go get github.com/jackc/pgx/v5
$ go get github.com/testcontainers/testcontainers-go
$ go get github.com/testcontainers/testcontainers-go/modules/postgres
$ go get github.com/stretchr/testify

Create Customer struct

Create a types.go file in the customer package and define the Customer struct to model the customer details:

package customer

type Customer struct {
	Id    int
	Name  string
	Email string
}

Create Repository

Next, create customer/repo.go, define the Repository struct, and add methods to create a customer and get a customer by email:

package customer

import (
	"context"
	"fmt"
	"os"

	"github.com/jackc/pgx/v5"
)

type Repository struct {
	conn *pgx.Conn
}

func NewRepository(ctx context.Context, connStr string) (*Repository, error) {
	conn, err := pgx.Connect(ctx, connStr)
	if err != nil {
		_, _ = fmt.Fprintf(os.Stderr, "Unable to connect to database: %v\n", err)
		return nil, err
	}
	return &Repository{
		conn: conn,
	}, nil
}

func (r Repository) CreateCustomer(ctx context.Context, customer Customer) (Customer, error) {
	err := r.conn.QueryRow(ctx,
		"INSERT INTO customers (name, email) VALUES ($1, $2) RETURNING id",
		customer.Name, customer.Email).Scan(&customer.Id)
	return customer, err
}

func (r Repository) GetCustomerByEmail(ctx context.Context, email string) (Customer, error) {
	var customer Customer
	query := "SELECT id, name, email FROM customers WHERE email = $1"
	err := r.conn.QueryRow(ctx, query, email).
		Scan(&customer.Id, &customer.Name, &customer.Email)
	if err != nil {
		return Customer{}, err
	}
	return customer, nil
}

Here's what the code does:

Write tests with Testcontainers

You have the Repository implementation ready, but for testing you need a PostgreSQL database. You can use testcontainers-go to spin up a Postgres database in a Docker container and run your tests against that database.

Set up the test database

In real applications you might use a database migration tool, but for this guide, use a script to initialize the database.

Create a testdata/init-db.sql file to create the CUSTOMERS table and insert sample data:

CREATE TABLE IF NOT EXISTS customers (id serial, name varchar(255), email varchar(255));

INSERT INTO customers(name, email) VALUES ('John', 'john@gmail.com');

Understand the testcontainers-go API

The testcontainers-go library provides the generic Container abstraction that can run any containerized service. To further simplify, testcontainers-go provides technology-specific modules that reduce boilerplate and provide a functional options pattern to construct the container instance.

For example, PostgresContainer provides WithDatabase(), WithUsername(), WithPassword(), and other functions to set various properties of Postgres containers.

Write the test

Create the customer/repo_test.go file and implement the test:

package customer

import (
	"context"
	"path/filepath"
	"testing"

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

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

	ctr, err := postgres.Run(ctx,
		"postgres:16-alpine",
		postgres.WithInitScripts(filepath.Join("..", "testdata", "init-db.sql")),
		postgres.WithDatabase("test-db"),
		postgres.WithUsername("postgres"),
		postgres.WithPassword("postgres"),
		postgres.BasicWaitStrategies(),
	)
	testcontainers.CleanupContainer(t, ctr)
	require.NoError(t, err)

	connStr, err := ctr.ConnectionString(ctx, "sslmode=disable")
	require.NoError(t, err)

	customerRepo, err := NewRepository(ctx, connStr)
	require.NoError(t, err)

	c, err := customerRepo.CreateCustomer(ctx, Customer{
		Name:  "Henry",
		Email: "henry@gmail.com",
	})
	assert.NoError(t, err)
	assert.NotNil(t, c)

	customer, err := customerRepo.GetCustomerByEmail(ctx, "henry@gmail.com")
	assert.NoError(t, err)
	assert.NotNil(t, customer)
	assert.Equal(t, "Henry", customer.Name)
	assert.Equal(t, "henry@gmail.com", customer.Email)
}

Here's what the test does:

Reuse containers with test suites

In the previous section, you saw how to spin up a Postgres Docker container for a single test. But often you have multiple tests in a single file, and you may want to reuse the same Postgres Docker container for all of them.

You can use the testify suite package to implement common test setup and teardown actions.

Extract container setup

First, extract the PostgresContainer creation logic into a separate file called testhelpers/containers.go:

package testhelpers

import (
	"context"
	"path/filepath"
	"testing"

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

type PostgresContainer struct {
	*postgres.PostgresContainer
	ConnectionString string
}

func CreatePostgresContainer(t *testing.T, ctx context.Context) *PostgresContainer {
	t.Helper()

	ctr, err := postgres.Run(ctx,
		"postgres:16-alpine",
		postgres.WithInitScripts(filepath.Join("..", "testdata", "init-db.sql")),
		postgres.WithDatabase("test-db"),
		postgres.WithUsername("postgres"),
		postgres.WithPassword("postgres"),
		postgres.BasicWaitStrategies(),
	)
	testcontainers.CleanupContainer(t, ctr)
	require.NoError(t, err)

	connStr, err := ctr.ConnectionString(ctx, "sslmode=disable")
	require.NoError(t, err)

	return &PostgresContainer{
		PostgresContainer: ctr,
		ConnectionString:  connStr,
	}
}

In containers.go, PostgresContainer extends the testcontainers-go PostgresContainer to provide easy access to ConnectionString. The CreatePostgresContainer() function accepts *testing.T as its first parameter, calls t.Helper() so that test failures point to the caller, and uses testcontainers.CleanupContainer() to register automatic cleanup.

Write the test suite

Create customer/repo_suite_test.go and implement tests for creating a customer and getting a customer by email using the testify suite package:

package customer

import (
	"context"
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"github.com/stretchr/testify/suite"
	"github.com/testcontainers/testcontainers-go-demo/testhelpers"
)

type CustomerRepoTestSuite struct {
	suite.Suite
	pgContainer *testhelpers.PostgresContainer
	repository  *Repository
	ctx         context.Context
}

func (suite *CustomerRepoTestSuite) SetupSuite() {
	suite.ctx = context.Background()
	suite.pgContainer = testhelpers.CreatePostgresContainer(suite.T(), suite.ctx)

	repository, err := NewRepository(suite.ctx, suite.pgContainer.ConnectionString)
	require.NoError(suite.T(), err)
	suite.repository = repository
}

func (suite *CustomerRepoTestSuite) TestCreateCustomer() {
	t := suite.T()

	customer, err := suite.repository.CreateCustomer(suite.ctx, Customer{
		Name:  "Henry",
		Email: "henry@gmail.com",
	})
	require.NoError(t, err)
	assert.NotNil(t, customer.Id)
}

func (suite *CustomerRepoTestSuite) TestGetCustomerByEmail() {
	t := suite.T()

	customer, err := suite.repository.GetCustomerByEmail(suite.ctx, "john@gmail.com")
	require.NoError(t, err)
	assert.Equal(t, "John", customer.Name)
	assert.Equal(t, "john@gmail.com", customer.Email)
}

func TestCustomerRepoTestSuite(t *testing.T) {
	suite.Run(t, new(CustomerRepoTestSuite))
}

Here's what the code does:

[!TIP] For the purpose of this guide, the tests don't reset data in the database. In practice, it's a good idea to reset the database to a known state before running each test.

Run tests and next steps

Run the tests

Run all the tests using go test ./.... Optionally add the -v flag for verbose output:

$ go test -v ./...

You should see two Postgres Docker containers start automatically: one for the suite and its two tests, and another for the initial standalone test. All tests should pass. After the tests finish, the containers are stopped and removed automatically.

Summary

The Testcontainers for Go library helps you write integration tests by using the same type of database (Postgres) that you use in production, instead of mocks. Because you aren't using mocks and instead talk to real services, you're free to refactor code and still verify that the application works as expected.

To learn more about Testcontainers, visit the Testcontainers overview.

Further reading


版本号 #2
由 壳壳虫 创建于 2026-10-31 18:48:07 UTC
由 壳壳虫 更新于 2026-10-31 18:48:09 UTC