Claude Code Plugins

Community-maintained marketplace

Feedback

Write comprehensive Go unit tests using table-driven patterns with testify assertions

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

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

  1. Examine existing tests first to learn project conventions
  2. Use table-driven tests with t.Run() subtests
  3. Name test cases descriptively - should read like documentation
  4. Cover these scenarios:
    • Happy path (valid inputs)
    • Edge cases (empty, nil, zero, max values)
    • Error conditions (invalid input, failures)
    • Boundary values
  5. 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/assert and require for assertions
  • Test files live alongside source: foo.gofoo_test.go
  • Use tt as the test case variable name
  • Use got for actual results, expected or want for expected values
  • Run make test to verify tests pass
  • Run make test-coverage to 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: TestFunctionName or TestStructName_MethodName
  • Subtests: Descriptive lowercase with spaces: "empty input returns error"
  • Names should read like documentation of behavior

Edge Cases to Always Consider

  • nil pointers 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