# Golang Testing

> When to activate: Go tests, table-driven tests, testify, mocks, subtests, benchmarks, fuzz testing, test helpers

- Skill: `mattakushi432/golang-testing` (Agent Skill)
- Install (CLI): `npx skillmds@latest add mattakushi432/golang-testing`
- Raw SKILL.md: https://api.skillmd.com/api/skills/mattakushi432/golang-testing/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: Mattakushi432 (https://skillmd.com/u/mattakushi432)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/mattakushi432/golang-testing

---


# Go Testing Patterns

## Table-Driven Tests

```go
func TestAdd(t *testing.T) {
    tests := []struct {
        name     string
        a, b     int
        expected int
    }{
        {"positive numbers", 2, 3, 5},
        {"negative + positive", -1, 4, 3},
        {"zeros", 0, 0, 0},
    }

    for _, tc := range tests {
        t.Run(tc.name, func(t *testing.T) {
            got := Add(tc.a, tc.b)
            if got != tc.expected {
                t.Errorf("Add(%d, %d) = %d; want %d", tc.a, tc.b, got, tc.expected)
            }
        })
    }
}
```

## testify for Assertions

```go
import (
    "testing"
    "github.com/stretchr/testify/assert"
    "github.com/stretchr/testify/require"
)

func TestCreateUser(t *testing.T) {
    user, err := CreateUser("alice@example.com", "Alice")

    require.NoError(t, err)      // stops test on failure
    require.NotNil(t, user)

    assert.Equal(t, "alice@example.com", user.Email)
    assert.Equal(t, "Alice", user.Name)
    assert.NotEmpty(t, user.ID)
    assert.WithinDuration(t, time.Now(), user.CreatedAt, time.Second)
}
```

## Interface Mocking

```go
// Interface to mock
type EmailSender interface {
    Send(to, subject, body string) error
}

// Manual mock
type MockEmailSender struct {
    SentMessages []struct{ To, Subject, Body string }
    Err          error
}

func (m *MockEmailSender) Send(to, subject, body string) error {
    m.SentMessages = append(m.SentMessages, struct{ To, Subject, Body string }{to, subject, body})
    return m.Err
}

// Test
func TestRegistration_SendsWelcomeEmail(t *testing.T) {
    sender := &MockEmailSender{}
    svc := NewRegistrationService(sender)

    err := svc.Register("bob@example.com")

    require.NoError(t, err)
    require.Len(t, sender.SentMessages, 1)
    assert.Equal(t, "bob@example.com", sender.SentMessages[0].To)
    assert.Contains(t, sender.SentMessages[0].Subject, "Welcome")
}
```

## HTTP Handler Testing

```go
func TestGetArticle(t *testing.T) {
    repo := &MockArticleRepo{article: sampleArticle}
    handler := NewArticleHandler(repo)

    req := httptest.NewRequest(http.MethodGet, "/articles/123", nil)
    w := httptest.NewRecorder()

    handler.GetArticle(w, req)

    res := w.Result()
    assert.Equal(t, http.StatusOK, res.StatusCode)
    assert.Equal(t, "application/json", res.Header.Get("Content-Type"))

    var got Article
    json.NewDecoder(res.Body).Decode(&got)
    assert.Equal(t, sampleArticle.Title, got.Title)
}
```

## Test Helpers

```go
// Return a cleanup function for resources
func setupTestDB(t *testing.T) (*sql.DB, func()) {
    t.Helper()
    db, err := sql.Open("postgres", testDSN)
    require.NoError(t, err)
    require.NoError(t, runMigrations(db))

    return db, func() {
        db.Exec("TRUNCATE TABLE articles")
        db.Close()
    }
}

func TestDBIntegration(t *testing.T) {
    db, cleanup := setupTestDB(t)
    defer cleanup()
    // ... test using db
}
```

## Benchmarks

```go
func BenchmarkFibonacci(b *testing.B) {
    for i := 0; i < b.N; i++ {
        Fibonacci(20)
    }
}

// With setup outside the measured loop
func BenchmarkSort(b *testing.B) {
    data := generateData(10000)
    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        b.StopTimer()
        input := make([]int, len(data))
        copy(input, data)
        b.StartTimer()
        sort.Ints(input)
    }
}
```

## Fuzz Testing (Go 1.18+)

```go
func FuzzParseURL(f *testing.F) {
    // Seed corpus
    f.Add("https://example.com/path?key=value")
    f.Add("http://localhost:8080")
    f.Add("not-a-url")

    f.Fuzz(func(t *testing.T, input string) {
        // Must not panic
        u, err := ParseURL(input)
        if err == nil {
            // Valid parse must produce a round-trippable result
            assert.NotEmpty(t, u.Scheme)
        }
    })
}
```

## Common Anti-Patterns

- **`TestMain` for all tests** — only use when you genuinely need global setup/teardown
- **Parallel tests with shared state** — call `t.Parallel()` only when tests are truly independent
- **Not calling `t.Helper()`** in helper functions — call it so error lines point to the test, not the helper
- **Sleep-based synchronization** — use channels or `sync.WaitGroup` instead
- **Testing unexported functions** — test behavior through the public API

