Testing applications that integrate with external services like a database is always a bit complicated. Mocking those dependencies usually results in the most performant solution but always comes with the risk that the mock doesn’t behave like the real thing. Docker made this situation much simpler for at least some external dependencies.
Recently, I needed to test some code that relied on a PostgreSQL database being available. Mocking would have been very complicated and so I decided to just launch a PostgreSQL server inside a docker container, run the migrations, prepare the relevant database structure and then execute my tests. While setting all that up I ran into the dockertest library by ORY which makes launching Docker containers as part of your Go test suite relatively straight forward:
// +build integration
package integration_test
import (
"context"
"database/sql"
"fmt"
"path/filepath"
"testing"
"time"
sqlbuilder "github.com/huandu/go-sqlbuilder"
"github.com/ory/dockertest"
"github.com/stretchr/testify/require"
"project.com/pkg/db"
)
func dbURLFromResource(r *dockertest.Resource) string {
port := r.GetPort("5432/tcp")
return fmt.Sprintf("postgres://user:password@localhost:%s/project?sslmode=disable", port)
}
func TestSomething(t *testing.T) {
// Create test database inside a Docker container
ctx := context.Background()
pool, err := dockertest.NewPool("")
require.NoError(t, err)
res, err := pool.Run("postgres", "11.2-alpine", []string{
"POSTGRES_PASSWORD=password",
"POSTGRES_USER=user",
"POSTGRES_DB=project",
})
// The db.Setup function waits for the DB server
// to be completely available and then also
// runs some migrations
conn, err := db.Setup(ctx, func(c *db.Configuration) {
c.URL = dbURLFromResource(res)
})
require.NoError(t, err)
require.NotNil(t, conn)
defer pool.Purge(res)
// Now let's set up some test data:
ib := sqlbuilder.PostgreSQL.NewInsertBuilder()
query, args := ib.InsertInto("users").Cols("username").Values("testuser").Build()
_, err = conn.ExecContext(ctx, query, args...)
require.NoError(t, err)
tx, err := conn.BeginTx(ctx, nil)
require.NoError(t, err)
defer tx.Rollback()
// And here come the tests:
// ...
}
dockertest abstracts all interactions with the underlying Docker API through a pool (which handles the API client) and resources (which represent the launched containers).
From each resource you can then retrieve things like the local port
bindings so that you can interact with the services in them. This is
pretty much what the dbURLFromResource
function in the example above
does: It looks up the metadata of the database container and returns a
URL that I can then use for sql.Open
.
dockertest also offers a handful other feature like containers that are automatically removed after a certain timeout but so far I’ve only used the setup shown in the example above. Combined with testify and sqlbuilder and migrate for running schema migrations I think I have a quite powerful toolbox for handling PostgreSQL (or other DBs) integration tests π
Do you want to give me feedback about this article in private? Please send it to comments@zerokspot.com.
Alternatively, this website also supports Webmentions. If you write a post on a blog that supports this technique, I should get notified about your link π