Go Testing Core Knowledge
Deep Knowledge: Use mcp__documentation__fetch_docs with technology: go for comprehensive documentation.
Full Reference: See advanced.md for HTTP Testing, Benchmarks, Test Helpers, Test Fixtures, Integration Tests, Parallel Tests, and Test Coverage.
When NOT to Use This Skill
-
JavaScript/TypeScript Projects - Use vitest or jest
-
Java Projects - Use junit for Java testing
-
Python Projects - Use pytest for Python
-
E2E Browser Testing - Use Playwright or Selenium
-
Rust Projects - Use rust-testing skill
Basic Testing
Unit Tests
// math.go package math
func Add(a, b int) int { return a + b }
func Divide(a, b float64) (float64, error) { if b == 0 { return 0, errors.New("division by zero") } return a / b, nil }
// math_test.go package math
import "testing"
func TestAdd(t *testing.T) { result := Add(2, 3) if result != 5 { t.Errorf("Add(2, 3) = %d; want 5", result) } }
func TestDivide(t *testing.T) { result, err := Divide(10, 2) if err != nil { t.Fatalf("unexpected error: %v", err) } if result != 5.0 { t.Errorf("Divide(10, 2) = %f; want 5.0", result) } }
func TestDivideByZero(t *testing.T) { _, err := Divide(10, 0) if err == nil { t.Error("expected error for division by zero") } }
Running Tests
Run all tests
go test ./...
Run tests in current package
go test
Run specific test
go test -run TestAdd
Verbose output
go test -v
Run with coverage
go test -cover
Generate coverage report
go test -coverprofile=coverage.out go tool cover -html=coverage.out
Run tests with race detector
go test -race
Set timeout
go test -timeout 30s
Table-Driven Tests
func TestAddTableDriven(t *testing.T) { tests := []struct { name string a, b int expected int }{ {"positive numbers", 2, 3, 5}, {"negative numbers", -1, -1, -2}, {"zero", 0, 0, 0}, {"mixed", -1, 1, 0}, }
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)
}
})
}
}
func TestDivideTableDriven(t *testing.T) { tests := []struct { name string a, b float64 expected float64 expectErr bool }{ {"normal division", 10, 2, 5, false}, {"division by zero", 10, 0, 0, true}, {"negative result", -10, 2, -5, false}, }
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := Divide(tt.a, tt.b)
if tt.expectErr {
if err == nil {
t.Error("expected error, got nil")
}
return
}
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if result != tt.expected {
t.Errorf("got %f; want %f", result, tt.expected)
}
})
}
}
Testify
go get github.com/stretchr/testify
Assert Package
import ( "testing" "github.com/stretchr/testify/assert" )
func TestWithAssert(t *testing.T) { // Equality assert.Equal(t, 5, Add(2, 3)) assert.NotEqual(t, 6, Add(2, 3))
// Boolean
assert.True(t, true)
assert.False(t, false)
// Nil checks
assert.Nil(t, nil)
assert.NotNil(t, "value")
// Error checks
_, err := Divide(10, 0)
assert.Error(t, err)
assert.EqualError(t, err, "division by zero")
result, err := Divide(10, 2)
assert.NoError(t, err)
assert.Equal(t, 5.0, result)
// Contains
assert.Contains(t, "hello world", "world")
assert.Contains(t, []int{1, 2, 3}, 2)
// Length
assert.Len(t, []int{1, 2, 3}, 3)
// Panics
assert.Panics(t, func() { panic("boom") })
}
Require Package
import ( "testing" "github.com/stretchr/testify/require" )
func TestWithRequire(t *testing.T) { // require stops test on failure (unlike assert) result, err := Divide(10, 2) require.NoError(t, err) // Test stops here if err != nil require.Equal(t, 5.0, result) }
Suite Package
import ( "testing" "github.com/stretchr/testify/suite" )
type UserServiceTestSuite struct { suite.Suite db *sql.DB service *UserService }
func (s *UserServiceTestSuite) SetupSuite() { // Run once before all tests var err error s.db, err = sql.Open("postgres", "test-connection") s.Require().NoError(err) }
func (s *UserServiceTestSuite) TearDownSuite() { // Run once after all tests s.db.Close() }
func (s *UserServiceTestSuite) SetupTest() { // Run before each test s.service = NewUserService(s.db) }
func (s *UserServiceTestSuite) TearDownTest() { // Run after each test s.db.Exec("DELETE FROM users") }
func (s *UserServiceTestSuite) TestCreateUser() { user, err := s.service.Create("alice", "alice@example.com") s.Require().NoError(err) s.Assert().Equal("alice", user.Name) s.Assert().NotZero(user.ID) }
func TestUserServiceSuite(t *testing.T) { suite.Run(t, new(UserServiceTestSuite)) }
Mocking with gomock
go install go.uber.org/mock/mockgen@latest
Generate Mocks
// user_repository.go //go:generate mockgen -source=user_repository.go -destination=mocks/user_repository_mock.go -package=mocks
type UserRepository interface { Get(id string) (*User, error) Create(user *User) error Update(user *User) error Delete(id string) error }
Generate mocks
go generate ./...
Using Mocks
import ( "testing" "github.com/stretchr/testify/assert" "go.uber.org/mock/gomock" "myapp/mocks" )
func TestUserService_GetUser(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish()
mockRepo := mocks.NewMockUserRepository(ctrl)
// Set expectations
expectedUser := &User{ID: "1", Name: "Alice"}
mockRepo.EXPECT().
Get("1").
Return(expectedUser, nil).
Times(1)
// Create service with mock
service := NewUserService(mockRepo)
// Test
user, err := service.GetUser("1")
assert.NoError(t, err)
assert.Equal(t, "Alice", user.Name)
}
func TestUserService_CreateUser(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish()
mockRepo := mocks.NewMockUserRepository(ctrl)
// Match any user with specific name
mockRepo.EXPECT().
Create(gomock.Any()).
DoAndReturn(func(u *User) error {
u.ID = "generated-id"
return nil
}).
Times(1)
service := NewUserService(mockRepo)
user, err := service.CreateUser("Bob", "bob@example.com")
assert.NoError(t, err)
assert.Equal(t, "generated-id", user.ID)
}
gomock Matchers
func TestWithMatchers(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish()
mock := mocks.NewMockUserRepository(ctrl)
// Any value
mock.EXPECT().Get(gomock.Any()).Return(nil, nil)
// Specific value
mock.EXPECT().Get(gomock.Eq("123")).Return(nil, nil)
// Not equal
mock.EXPECT().Get(gomock.Not("")).Return(nil, nil)
// Custom matcher
userWithName := gomock.Cond(func(u interface{}) bool {
user, ok := u.(*User)
return ok && user.Name != ""
})
mock.EXPECT().Create(userWithName).Return(nil)
}
Call Order
func TestCallOrder(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish()
mock := mocks.NewMockUserRepository(ctrl)
// InOrder ensures calls happen in sequence
gomock.InOrder(
mock.EXPECT().Get("1").Return(&User{ID: "1"}, nil),
mock.EXPECT().Update(gomock.Any()).Return(nil),
)
// Use mock...
}
Checklist
-
Unit tests for all public functions
-
Table-driven tests for multiple cases
-
testify/assert for cleaner assertions
-
Mock external dependencies
-
HTTP handler tests
-
Benchmarks for performance-critical code
-
Integration tests with build tags
-
Parallel tests where appropriate
-
Coverage reporting in CI
Anti-Patterns
Anti-Pattern Why It's Bad Solution
Not using table-driven tests Duplicate test code Use []struct{} for test cases
Testing unexported functions Coupled to implementation Test through exported API
Not using t.Helper() Confusing error line numbers Mark helper functions with t.Helper()
Sharing test state Flaky, order-dependent Isolate setup in each test
Not using subtests All cases fail if one fails Use t.Run() for subtests
Ignoring race detector Hidden concurrency bugs Run tests with -race flag
Not benchmarking Performance regressions Add benchmarks for critical paths
Quick Troubleshooting
Problem Likely Cause Solution
"No tests found" Wrong file naming Use *_test.go naming
Race condition detected Shared mutable state Fix data races, use sync primitives
Mock not called Wrong method or args Check gomock expectations
Coverage not accurate Missing test files Run go test ./... for all packages
Test timeout Infinite loop or slow operation Add -timeout flag, investigate
"interface conversion" panic Wrong mock interface Ensure mock implements correct interface
Reference Documentation
-
Unit Tests
-
Testify
-
Mocking
-
Advanced Patterns