Testing in Go: unit, integration, e2e — complete guide

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Introduction: Why testing in Go is fundamental



Go (Golang) was created with simplicity and reliability in mind. The built-in testing package is one of the best tools for writing tests among modern languages. It doesn't require third-party libraries for basic scenarios, yet it is easily extensible. In this article, we will cover three levels of testing: unit, integration, and e2e. You will learn how to write them, when to apply them, and which tools to use. The material will be useful for both beginners and experienced Go developers.



1. Unit tests in Go: the foundation



Unit tests check individual functions or methods in isolation. In Go, you just need to create a file with the _test.go suffix and write functions like TestXxx(t *testing.T).



Simple unit test example



Suppose we have an addition function:



// math.gopackage main

func Add(a, b int) int { return a + b}


The test for it:



// math_test.gopackage main

import "testing"

func TestAdd(t *testing.T) { result := Add(2, 3) expected := 5 if result != expected { t.Errorf("Add(2, 3) = %d; want %d", result, expected) }}


Table-Driven Tests



The Go community recommends using table-driven tests for checking multiple cases:



func TestAddTable(t *testing.T) {    tests := []struct {        name string        a, b int        want int    }{        {"positive", 2, 3, 5},        {"negative", -1, -2, -3},        {"zero", 0, 0, 0},    }    for _, tt := range tests {        t.Run(tt.name, func(t *testing.T) {            if got := Add(tt.a, tt.b); got != tt.want {                t.Errorf("Add() = %d, want %d", got, tt.want)            }        })    }}


Unit tests are run with the command:



go test ./...


2. Integration tests: checking interaction



Integration tests check how different parts of the system work together: database, API, file system. In Go, they are typically placed in separate files with a build tag or in an integration package.



Using build tags



Create a file integration_test.go with a tag:



//go:build integration

package integration

import ( "database/sql" "testing" _ "github.com/lib/pq")

func TestDatabaseConnection(t *testing.T) { dsn := "host=localhost port=5432 user=test dbname=test sslmode=disable" db, err := sql.Open("postgres", dsn) if err != nil { t.Fatalf("Failed to open DB: %v", err) } defer db.Close()

err = db.Ping() if err != nil { t.Fatalf("Failed to ping DB: %v", err) }}


Running only integration tests:



go test -tags=integration ./...


Working with a test database



For integration tests, testcontainers-go or in-memory databases (e.g., SQLite) are often used. Example with testcontainers:



func TestWithPostgresContainer(t *testing.T) {    ctx := context.Background()    pgContainer, err := postgres.RunContainer(ctx,        testcontainers.WithImage("postgres:15"),        postgres.WithDatabase("testdb"),    )    require.NoError(t, err)    defer pgContainer.Terminate(ctx)

dsn, _ := pgContainer.ConnectionString(ctx) // use dsn for connection}


3. E2E tests: checking the entire system



End-to-End tests simulate user behavior and check the entire system from start to finish. In Go, they are often written for HTTP services using the net/http/httptest package.



E2E test example for a REST API



func TestCreateUserE2E(t *testing.T) {    // S                

Blogs

Book Recommendations