Using Docker to Run Unit and Integration Tests with Go

Docker has revolutionized the way we develop, build, and ship applications. It offers consistent, repeatable, and isolated environments, which makes it an ideal choice for running tests. In this blog post, we'll explore how to use Docker to run unit and integration tests for a Go application.

Setting up a Basic Go Project

Before we dive into Docker, let's set up a simple Go application.

1. Create a new directory:

mkdir go-docker-test
cd go-docker-test

2. Initialize a Go module:

go mod init go-docker-test

3. Write a simple Go function and its test. Create a file named main.go:

package main

// Add sums two integers
func Add(x, y int) int {
    return x + y
}

4. Now, create a main_test.go:

package main

import (
    "testing"
)

func TestAdd(t *testing.T) {
    if Add(2, 3) != 5 {
        t.Fail()
    }
}

Dockerizing the Go Tests

1. Create a Dockerfile in the project root:

# Use the official Go image as a base image
FROM golang:1.17

# Set the working directory inside the container
WORKDIR /app

# Copy the go mod and sum files
COPY go.mod go.sum ./

# Download all dependencies
RUN go mod download

# Copy the source from the current directory to the working directory inside the container
COPY . .

# Command to run the tests
CMD ["go", "test", "-v", "./..."]

2. Build the Docker image:

docker build -t go-docker-test .

3. Run tests using Docker:

Hello, World!

Integration Tests with Docker Compose

Imagine we have an integration test where our Go app interacts with a PostgreSQL database. Docker Compose can manage multi-container applications, making it easier to spin up our app alongside its dependencies.

1. First, add Docker Compose by creating a docker-compose.yml:

version: '3'

services:
  app:
    build:
      context: .
      dockerfile: Dockerfile
    depends_on:
      - db

  db:
    image: postgres:13
    environment:
      POSTGRES_USER: user
      POSTGRES_PASSWORD: password
      POSTGRES_DB: testdb

2. Add a new file db.go to include an integration that interacts with the database (using a Go PostgreSQL driver like lib/pq).

package main

import (
	"database/sql"
	"fmt"
	"log"

	_ "github.com/lib/pq"
)

var db *sql.DB

func initDB() {
	connStr := "user=user dbname=testdb password=password sslmode=disable"
	var err error
	db, err = sql.Open("postgres", connStr)
	if err != nil {
		log.Fatalf("Failed to connect: %v", err)
	}
}

func insertName(name string) error {
	_, err := db.Exec("INSERT INTO names (name) VALUES ($1)", name)
	return err
}

func getName() (string, error) {
	var name string
	err := db.QueryRow("SELECT name FROM names LIMIT 1").Scan(&name)
	return name, err
}

3. Add db_test.go to test the integration between the new Go code and PostgreSQL.

package main

import (
	"testing"
)

func TestIntegration(t *testing.T) {
	initDB()

	// Setup: Create a table for testing.
	_, err := db.Exec("CREATE TABLE names (id serial primary key, name varchar(50))")
	if err != nil {
		t.Fatalf("Failed to create table: %v", err)
	}
	defer db.Exec("DROP TABLE names")

	// Insert a name and retrieve it
	name := "John Doe"
	if err := insertName(name); err != nil {
		t.Fatalf("Failed to insert name: %v", err)
	}

	retrievedName, err := getName()
	if err != nil {
		t.Fatalf("Failed to get name: %v", err)
	}

	if retrievedName != name {
		t.Fatalf("Expected %s, but got %s", name, retrievedName)
	}
}

3. Use Docker Compose to run tests:

With this setup, Docker Compose will start the db service first (PostgreSQL), then our app service. The app service will execute our tests, which now include both unit and integration tests.

Conclusion

Docker and Docker Compose make it simpler to run unit and integration tests in isolated, repeatable environments. By dockerizing our Go tests, we ensure that they run consistently across various platforms and setups. Whether you're working solo or in a large team, incorporating Docker into your testing workflow can significantly improve the reliability and consistency of your tests.

Previous
Previous

Setting Up an API Gateway Using NGINX

Next
Next

Utilizing Protocol Buffers (Protobuf) to Create API Contracts Between Web Server and Client