| name | go-testing |
| description | Go unit testing standards. Use when writing, generating, or reviewing Go test code. |
| allowed-tools | Read, Edit, Write, Bash, Grep, Glob |
Go Unit Testing Skill
When to Use
Invoke this skill when writing, improving, or reviewing Go unit tests.
Instructions
- Examine existing tests first to learn project conventions
- Use table-driven tests with
t.Run()subtests - Name test cases descriptively - should read like documentation
- Cover these scenarios:
- Happy path (valid inputs)
- Edge cases (empty, nil, zero, max values)
- Error conditions (invalid input, failures)
- Boundary values
- Do not test metrics. Use nil for metric objects
Test Structure Template
func TestFunctionName(t *testing.T) {
tests := []struct {
name string
input InputType
expected OutputType
wantErr bool
}{
{"descriptive name", input, expected, false},
{"error case", badInput, zero, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := FunctionUnderTest(tt.input)
if tt.wantErr {
require.Error(t, err)
return
}
require.NoError(t, err)
assert.Equal(t, tt.expected, got)
})
}
}
Project-Specific Conventions
- Use
github.com/stretchr/testify/assertandrequirefor assertions - Test files live alongside source:
foo.go→foo_test.go - Use
ttas the test case variable name - Use
gotfor actual results,expectedorwantfor expected values - Run
make testto verify tests pass - Run
make test-coverageto check coverage
Assertions: require vs assert
require: Fails immediately, stops test execution. Use for setup validation and fatal errors.assert: Records failure but continues. Use for non-fatal checks.
require.NoError(t, err, "setup must succeed") // Fatal if fails
assert.Equal(t, expected, got) // Continue on failure
Helper Functions
Data Builders with Variadic Optional Params
Create helpers that make test cases readable with sensible defaults:
func createOrder(id string, buyerAddress ...string) *Order {
var addr string
if len(buyerAddress) > 0 {
addr = buyerAddress[0]
}
return &Order{ID: id, BuyerAddress: addr}
}
Test-Local Builders
Define builders inside test functions when only used once:
func TestOrders(t *testing.T) {
newOrder := func(id string) *Order {
return &Order{ID: id}
}
// tests use newOrder()...
}
Assertion Helpers with t.Helper()
Always call t.Helper() for proper error attribution:
func assertOrderIDs(t *testing.T, actual []Order, expectedIDs []string) {
t.Helper()
if len(actual) != len(expectedIDs) {
t.Errorf("expected %d orders, got %d", len(expectedIDs), len(actual))
}
}
Base Data Pattern
Create valid base objects, then modify per test case:
baseOrder := &Order{
ID: "order-1",
Amount: 100,
Status: "pending",
}
tests := []struct {
name string
order *Order
wantErr bool
}{
{
name: "valid order",
order: baseOrder,
},
{
name: "invalid amount",
order: &Order{ID: "order-1", Amount: -1, Status: "pending"}, // only Amount changed
wantErr: true,
},
}
Test Structure: Setup → Execute → Verify
t.Run(tt.name, func(t *testing.T) {
// Setup
store := createStore(tt.initialData...)
svc := NewService(store)
// Execute
result, err := svc.Process(tt.input)
// Verify
if tt.wantErr {
require.Error(t, err)
assert.Contains(t, err.Error(), tt.errContains)
return
}
require.NoError(t, err)
assert.Equal(t, tt.expected, result)
})
Mocking
Hand-Written Mocks (Preferred for Simple Interfaces)
Use for interfaces with few methods or simple behavior:
type mockStore struct {
orders map[string]*Order
}
func (m *mockStore) Get(id string) (*Order, error) {
order, ok := m.orders[id]
if !ok {
return nil, ErrNotFound
}
return order, nil
}
func createStore(orders ...*Order) *mockStore {
store := &mockStore{orders: make(map[string]*Order)}
for _, o := range orders {
store.orders[o.ID] = o
}
return store
}
testify/mock (For Complex Mocking)
Use when you need call verification, argument capture, or complex return sequences:
type MockClient struct {
mock.Mock
}
func (m *MockClient) Fetch(ctx context.Context, id string) (*Data, error) {
args := m.Called(ctx, id)
return args.Get(0).(*Data), args.Error(1)
}
// In test:
client := new(MockClient)
client.On("Fetch", mock.Anything, "id-1").Return(&Data{}, nil)
// ... test ...
client.AssertExpectations(t)
Test Naming
- Function:
TestFunctionNameorTestStructName_MethodName - Subtests: Descriptive lowercase with spaces:
"empty input returns error" - Names should read like documentation of behavior
Edge Cases to Always Consider
nilpointers and interfaces- Empty slices/maps/strings
- Zero values for numeric types
- Maximum/minimum values
- Concurrent access (use
t.Parallel()where safe) - Context cancellation
- Database/network errors
Quick Reference
| Principle | Implementation |
|---|---|
| Readability | Semantic struct fields, descriptive names |
| DRY | Helper functions with variadic optional params |
| Clarity | Base data pattern for test variations |
| Attribution | t.Helper() on assertion helpers |
| Structure | Setup → Execute → Verify |
| Assertions | testify require/assert |
| Scoping | Test-local builders when only used once |