Integration and System Testing

Overview

Integration tests verify components work together, testing against real databases, APIs, and external services.

Test layers

unit (many, fast)
   |
integration (fewer, realistic deps)
   |
system/e2e (fewest, highest confidence)

HTTP Testing

import "net/http/httptest"

func TestHandler(t *testing.T) {
    req := httptest.NewRequest("GET", "/users", nil)
    w := httptest.NewRecorder()

    handler(w, req)

    if w.Code != http.StatusOK {
        t.Errorf("status = %d; want 200", w.Code)
    }
}

Test Server

func TestAPIClient(t *testing.T) {
    server := httptest.NewServer(http.HandlerFunc(
        func(w http.ResponseWriter, r *http.Request) {
            w.Write([]byte(`{"id": 1}`))
        }))
    defer server.Close()

    client := NewClient(server.URL)
    user, err := client.GetUser(1)
    // Assert...
}

Database Testing

func TestUserRepository(t *testing.T) {
    if testing.Short() {
        t.Skip("skipping integration test")
    }

    db := setupTestDB(t)
    t.Cleanup(func() { db.Close() })

    repo := NewUserRepository(db)

    // Test operations
    err := repo.Create(&User{Name: "test"})
    if err != nil {
        t.Fatal(err)
    }
}

Build Tags

//go:build integration
// +build integration

package myapp_test

// Only runs with: go test -tags=integration

Environment-Based Tests

func TestWithEnv(t *testing.T) {
    url := os.Getenv("API_URL")
    if url == "" {
        t.Skip("API_URL not set")
    }
    // Test against real API
}

Docker Integration

func TestWithDocker(t *testing.T) {
    if testing.Short() {
        t.Skip("skipping docker test")
    }

    // Use testcontainers-go or similar
    container, err := startPostgres()
    if err != nil {
        t.Fatal(err)
    }
    t.Cleanup(func() { container.Terminate(ctx) })

    // Run tests against container
}

Go 1.26 Integration Testing Upgrades

Use a two-lane CI strategy:

  1. Default lane (every push): fast unit + short integration.
  2. Full lane (main/nightly): DB containers, external API contract checks, and race detector.
# default
go test ./... -short -shuffle=on

# full
go test ./... -tags=integration -race -count=1

For external APIs, always assert status + schema shape, not only status codes:

func assertUserShape(t *testing.T, body []byte) {
    t.Helper()
    var got map[string]any
    if err := json.Unmarshal(body, &got); err != nil {
        t.Fatal(err)
    }
    if _, ok := got["id"]; !ok {
        t.Fatal("missing id")
    }
}

Summary

Tool Use Case
httptest HTTP handlers
Build tags Separate test suites
Env vars External dependencies
testing.Short() Skip slow tests