Go Testing Patterns
Comprehensive Go testing patterns for writing reliable, maintainable tests following TDD methodology.
When to Activate
- •Writing new Go functions or methods
- •Adding test coverage to existing code
- •Creating benchmarks for performance-critical code
- •Implementing fuzz tests for input validation
- •Following TDD workflow in Go projects
TDD Workflow for Go
code
RED → Write a failing test first GREEN → Write minimal code to pass the test REFACTOR → Improve code while keeping tests green REPEAT → Continue with next requirement
Table-Driven Tests
go
func TestAdd(t *testing.T) {
tests := []struct {
name string
a, b int
expected int
}{
{"positive numbers", 2, 3, 5},
{"negative numbers", -1, -2, -3},
{"zero values", 0, 0, 0},
{"mixed signs", -1, 1, 0},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := Add(tt.a, tt.b)
if got != tt.expected {
t.Errorf("Add(%d, %d) = %d; want %d", tt.a, tt.b, got, tt.expected)
}
})
}
}
Table-Driven Tests with Error Cases
go
func TestParseConfig(t *testing.T) {
tests := []struct {
name string
input string
want *Config
wantErr bool
}{
{"valid config", `{"host":"localhost"}`, &Config{Host: "localhost"}, false},
{"invalid JSON", `{invalid}`, nil, true},
{"empty input", "", nil, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := ParseConfig(tt.input)
if tt.wantErr {
if err == nil {
t.Error("expected error, got nil")
}
return
}
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("got %+v; want %+v", got, tt.want)
}
})
}
}
Test Helpers
go
func setupTestDB(t *testing.T) *sql.DB {
t.Helper()
db, err := sql.Open("sqlite3", ":memory:")
if err != nil {
t.Fatalf("failed to open database: %v", err)
}
t.Cleanup(func() { db.Close() })
return db
}
func assertEqual[T comparable](t *testing.T, got, want T) {
t.Helper()
if got != want {
t.Errorf("got %v; want %v", got, want)
}
}
Interface-Based Mocking
go
type UserRepository interface {
GetUser(id string) (*User, error)
SaveUser(user *User) error
}
type MockUserRepository struct {
GetUserFunc func(id string) (*User, error)
SaveUserFunc func(user *User) error
}
func (m *MockUserRepository) GetUser(id string) (*User, error) {
return m.GetUserFunc(id)
}
func TestUserService(t *testing.T) {
mock := &MockUserRepository{
GetUserFunc: func(id string) (*User, error) {
return &User{ID: id, Name: "Alice"}, nil
},
}
service := NewUserService(mock)
user, err := service.GetUserProfile("123")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if user.Name != "Alice" {
t.Errorf("got name %q; want %q", user.Name, "Alice")
}
}
Benchmarks
go
func BenchmarkProcess(b *testing.B) {
data := generateTestData(1000)
b.ResetTimer()
for i := 0; i < b.N; i++ {
Process(data)
}
}
// Different sizes
func BenchmarkSort(b *testing.B) {
for _, size := range []int{100, 1000, 10000} {
b.Run(fmt.Sprintf("size=%d", size), func(b *testing.B) {
data := generateRandomSlice(size)
b.ResetTimer()
for i := 0; i < b.N; i++ {
tmp := make([]int, len(data))
copy(tmp, data)
sort.Ints(tmp)
}
})
}
}
Fuzzing (Go 1.18+)
go
func FuzzParseJSON(f *testing.F) {
f.Add(`{"name": "test"}`)
f.Add(`[]`)
f.Fuzz(func(t *testing.T, input string) {
var result map[string]interface{}
err := json.Unmarshal([]byte(input), &result)
if err != nil {
return
}
_, err = json.Marshal(result)
if err != nil {
t.Errorf("Marshal failed after Unmarshal: %v", err)
}
})
}
HTTP Handler Testing
go
func TestHealthHandler(t *testing.T) {
req := httptest.NewRequest(http.MethodGet, "/health", nil)
w := httptest.NewRecorder()
HealthHandler(w, req)
resp := w.Result()
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
t.Errorf("got status %d; want %d", resp.StatusCode, http.StatusOK)
}
}
Testing Commands
bash
go test ./... # Run all tests go test -v ./... # Verbose go test -run TestAdd ./... # Specific test go test -race ./... # Race detector go test -cover -coverprofile=c.out ./... # Coverage go tool cover -html=c.out # HTML report go test -bench=. -benchmem ./... # Benchmarks go test -fuzz=FuzzParse -fuzztime=30s # Fuzzing go test -count=10 ./... # Flaky detection
Best Practices
- •Write tests FIRST (TDD)
- •Use table-driven tests
- •Test behavior, not implementation
- •Use
t.Helper()in helpers - •Use
t.Parallel()for independent tests - •Clean up with
t.Cleanup() - •Use meaningful test names
- •Don't test private functions directly
- •Don't use
time.Sleep()in tests