Integration Testing Go with Docker: Complete Guide
\n\nIntegration testing is a critically important stage of development, especially in the world of microservices and distributed systems. Go (Golang) combined with Docker provides powerful and elegant tools for creating reliable integration tests. In this article, we will break down how to set up the environment, write tests for interacting with databases and external services using Docker, testcontainers-go, and the standard testing library.
1. Why Docker for Integration Testing?
\n\nTraditional unit tests isolate logic but do not verify interaction with real dependencies: databases, queues, APIs. Docker solves this problem by allowing:
\n- \n
- Environment Isolation: Each test runs a clean container, eliminating state conflicts. \n
- Repeatability: Identical environment on the local machine and in CI/CD. \n
- Fast Deployment: Containers start in seconds. \n
- Easy Cleanup: The container is destroyed after the test. \n
We will use the testcontainers-go library — it provides a high-level API for managing the container lifecycle directly from the test code.
2. Project Setup and Dependency Installation
\n\nLet's create a simple HTTP server in Go that saves data to PostgreSQL. Then we'll write an integration test that checks the endpoint.
\n\nProject Structure:
\nmyapp/\n├── main.go\n├── go.mod\n├── go.sum\n├── handlers/\n│ └── user.go\n└── integration_test.go\n\nStep 1: Initialize the module and install testcontainers:
\ngo mod init myapp\ngo get github.com/testcontainers/testcontainers-go\ngo get github.com/testcontainers/testcontainers-go/modules/postgres\ngo get github.com/lib/pq\ngo get github.com/stretchr/testify/assert\n\nStep 2: Write a simple handler in handlers/user.go:
package handlers\n\nimport (\n "database/sql"\n "encoding/json"\n "net/http"\n)\n\ntype User struct {\n Name string `json:"name"`\n Age int `json:"age"`\n}\n\nfunc CreateUser(db *sql.DB) http.HandlerFunc {\n return func(w http.ResponseWriter, r *http.Request) {\n var user User\n if err := json.NewDecoder(r.Body).Decode(&user); err != nil {\n http.Error(w, err.Error(), http.StatusBadRequest)\n return\n }\n _, err := db.Exec("INSERT INTO users (name, age) VALUES ($1, $2)", user.Name, user.Age)\n if err != nil {\n http.Error(w, err.Error(), http.StatusInternalServerError)\n return\n }\n w.WriteHeader(http.StatusCreated)\n json.NewEncoder(w).Encode(user)\n }\n}\n\n3. Writing the Integration Test with Docker
\n\nNow for the interesting part — a test that spins up PostgreSQL in Docker, creates a table, starts our server, and verifies its operation.
\n\nFile integration_test.go:
package myapp\n\nimport (\n "bytes"\n "context"\n "encoding/json"\n "net/http"\n "net/http/httptest"\n "testing"\n\n "myapp/handlers"\n\n "github.com/lib/pq"\n "github.com/stretchr/testify/assert"\n "github.com/testcontainers/testcontainers-go"\n "github.com/testcontainers/testcontainers-go/modules/postgres"\n "github.com/testcontainers/testcontainers-go/wait"\n)\n\nfunc TestCreateUserIntegration(t *testing.T) {\n ctx := context.Background()\n\n // 1. Start PostgreSQL container\n pgContainer, err := postgres.RunContainer(ctx,\n testcontainers.WithImage("postgres:15-alpine"),\n postgres.WithDatabase("testdb"),\n postgres.WithUsername("test"),\n postgres.