Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
315 changes: 315 additions & 0 deletions .claude/skills/testcontainers-guides-migrator/SKILL.md

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions _vale/config/vocabularies/Docker/accept.txt
Original file line number Diff line number Diff line change
Expand Up @@ -167,11 +167,14 @@ Paketo
PAT
perl
pgAdmin
pgx
PKG
plaintext
plist
pluggable
Postgres
psycopg
pytest
PowerShell
Python
Qualcomm
Expand Down
36 changes: 36 additions & 0 deletions content/guides/testcontainers-go-getting-started/_index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
---
title: Getting started with Testcontainers for Go
linkTitle: Testcontainers for Go
description: Learn how to use Testcontainers for Go to test database interactions with a real PostgreSQL instance.
keywords: testcontainers, go, golang, testing, postgresql, integration testing
summary: |
Learn how to create a Go application and test database interactions
using Testcontainers for Go with a real PostgreSQL instance.
toc_min: 1
toc_max: 2
tags: [testing-with-docker]
languages: [go]
params:
time: 20 minutes
---

<!-- Source: https://github.com/testcontainers/tc-guide-getting-started-with-testcontainers-for-go -->

In this guide, you will learn how to:

- Create a Go application with modules support
- Implement a Repository to manage customer data in a PostgreSQL database using the pgx driver
- Write integration tests using testcontainers-go
- Reuse containers across multiple tests using test suites

## Prerequisites

- Go 1.25+
- Your preferred IDE (VS Code, GoLand)

Check warning on line 29 in content/guides/testcontainers-go-getting-started/_index.md

View workflow job for this annotation

GitHub Actions / validate (vale)

[vale] reported by reviewdog 🐶 [Docker.RecommendedWords] Consider using 'versus' instead of 'VS' Raw Output: {"message": "[Docker.RecommendedWords] Consider using 'versus' instead of 'VS'", "location": {"path": "content/guides/testcontainers-go-getting-started/_index.md", "range": {"start": {"line": 29, "column": 23}}}, "severity": "INFO"}
- A Docker environment supported by Testcontainers. For details, see
the [testcontainers-go system requirements](https://golang.testcontainers.org/system_requirements/).

> [!NOTE]
> If you're new to Testcontainers, visit the
> [Testcontainers overview](https://testcontainers.com/getting-started/) to learn more about
> Testcontainers and the benefits of using it.
103 changes: 103 additions & 0 deletions content/guides/testcontainers-go-getting-started/create-project.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
---
title: Create the Go project
linkTitle: Create the project
description: Set up a Go project with a PostgreSQL-backed repository.
weight: 10
---

## Initialize the project

Start by creating a Go project.

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

This guide uses the [jackc/pgx](https://github.com/jackc/pgx) PostgreSQL
driver to interact with the Postgres database and the testcontainers-go
[Postgres module](https://golang.testcontainers.org/modules/postgres/) to
spin up a Postgres Docker instance for testing. It also uses
[testify](https://github.com/stretchr/testify) for running multiple tests
as a suite and for writing assertions.

Install these dependencies:

```console
$ 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:

```go
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:

```go
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:

- `Repository` holds a `*pgx.Conn` for performing database operations.
- `NewRepository(connStr)` takes a database connection string and initializes a `Repository`.
- `CreateCustomer()` and `GetCustomerByEmail()` are methods on the `Repository` receiver that insert and query customer records.
36 changes: 36 additions & 0 deletions content/guides/testcontainers-go-getting-started/run-tests.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
---
title: Run tests and next steps
linkTitle: Run tests
description: Run your Testcontainers-based integration tests and explore next steps.
weight: 40
---

## Run the tests

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

```console
$ 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](https://testcontainers.com/getting-started/).

## Further reading

- [Testcontainers for Go documentation](https://golang.testcontainers.org/)
- [Testcontainers for Go quickstart](https://golang.testcontainers.org/quickstart/)
- [Testcontainers Postgres module for Go](https://golang.testcontainers.org/modules/postgres/)
144 changes: 144 additions & 0 deletions content/guides/testcontainers-go-getting-started/test-suites.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
---
title: Reuse containers with test suites
linkTitle: Test suites
description: Share a single Postgres container across multiple tests using testify suites.
weight: 30
---

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](https://pkg.go.dev/github.com/stretchr/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`:

```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:

```go
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:

- `CustomerRepoTestSuite` extends `suite.Suite` and includes fields shared
across multiple tests.
- `SetupSuite()` runs once before all tests. It calls
`CreatePostgresContainer(suite.T(), ...)` which handles cleanup registration
automatically via `CleanupContainer`, so no `TearDownSuite()` is needed.
- `TestCreateCustomer()` uses `require.NoError()` for the create operation
(fail immediately if it errors) and `assert.NotNil()` for the ID check.
- `TestGetCustomerByEmail()` uses `require.NoError()` then asserts on the
returned values.
- `TestCustomerRepoTestSuite(t *testing.T)` runs the test suite when you
execute `go test`.

> [!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.
Loading
Loading