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:
- Calls
postgres.Run()with thepostgres:16-alpineDocker image as the first argument. This is the v0.41.0 API — the image is a required positional parameter instead of an option. - Configures initialization scripts using
WithInitScripts(...)so that theCUSTOMERStable is created and sample data is inserted after the database starts. - Uses
postgres.BasicWaitStrategies()which combines waiting for the Postgres log message and for the port to be ready. This replaces manual wait strategy configuration. - Calls
testcontainers.CleanupContainer(t, ctr)right afterpostgres.Run(). This registers automatic cleanup with the test framework, replacing the manualt.CleanupandTerminatepattern. - Obtains the database
ConnectionStringfrom the container and initializes aRepository. - Creates a customer with the email
henry@gmail.comand verifies that the customer exists in the database.