Overview
Go's testing package is built into the language with zero external dependencies. The table-driven test pattern is the idiomatic way to test multiple scenarios concisely.
Why This Matters
- -Built-in — no testing framework to install or configure
- -Table-driven — clean pattern for testing multiple inputs
- -Benchmarking — built-in performance testing with
go test -bench - -Coverage — native coverage reporting with
go test -cover
Step 1: Table-Driven Tests
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", 0, 0, 0},
{"mixed signs", -5, 10, 5},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := Add(tt.a, tt.b)
if result != tt.expected {
t.Errorf("Add(%d, %d) = %d, want %d", tt.a, tt.b, result, tt.expected)
}
})
}
}Step 2: Test Helpers
// t.Helper() marks this as a helper — errors show caller's line number
func assertEqual(t *testing.T, got, want interface{}) {
t.Helper()
if got != want {
t.Errorf("got %v, want %v", got, want)
}
}
func setupTestDB(t *testing.T) *Database {
t.Helper()
db, err := NewDatabase(":memory:")
if err != nil {
t.Fatalf("failed to create test db: %v", err)
}
t.Cleanup(func() { db.Close() })
return db
}Step 3: Mocking with Interfaces
// Define interface for the dependency
type UserRepository interface {
FindByID(ctx context.Context, id string) (*User, error)
Save(ctx context.Context, user *User) error
}
// Mock implementation for tests
type mockUserRepo struct {
users map[string]*User
}
func (m *mockUserRepo) FindByID(_ context.Context, id string) (*User, error) {
user, ok := m.users[id]
if !ok {
return nil, ErrNotFound
}
return user, nil
}
func TestUserService_GetUser(t *testing.T) {
repo := &mockUserRepo{
users: map[string]*User{"123": {ID: "123", Name: "Alice"}},
}
svc := NewUserService(repo)
user, err := svc.GetUser(context.Background(), "123")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if user.Name != "Alice" {
t.Errorf("got name %q, want %q", user.Name, "Alice")
}
}Step 4: Running Tests
# Run all tests
go test ./...
# Verbose output
go test -v ./...
# Run specific test
go test -run TestUserService ./pkg/users/
# With coverage
go test -cover ./...
go test -coverprofile=coverage.out ./...
go tool cover -html=coverage.out # Visual report
# Race detector
go test -race ./...
# Benchmarks
go test -bench=. -benchmem ./...
Best Practices
- -Use table-driven tests for functions with multiple input/output scenarios
- -Use
t.Helper() in test helper functions - -Use
t.Cleanup() for teardown (runs after test completes) - -Use
t.Parallel() for independent tests - -Mock dependencies through interfaces, not concrete types
- -Run with
-race flag in CI to detect data races
Common Mistakes
- -Not using t.Helper() in helper functions (wrong line numbers in errors)
- -Not running tests with -race flag (misses data races)
- -Testing private functions instead of public API
- -Complex test setup that should use test fixtures