Agent Skills: Go Testing

Go testing best practices including table-driven tests, race detection, test coverage, and mocking strategies. Use when writing or reviewing Go tests.

UncategorizedID: jovermier/cc-stack-marketplace/go-testing

Install this agent skill to your local

pnpm dlx add-skill https://github.com/jovermier/cc-stack-marketplace/tree/HEAD/plugins/cc-go/skills/go-testing

Skill Files

Browse the full folder contents for go-testing.

Download Skill

Loading file tree…

plugins/cc-go/skills/go-testing/SKILL.md

Skill Metadata

Name
go-testing
Description
Go testing best practices including table-driven tests, race detection, test coverage, and mocking strategies. Use when writing or reviewing Go tests.

Go Testing

Expert guidance for writing maintainable, effective Go tests.

Quick Reference

| Pattern | When to Use | Structure | |---------|-------------|-----------| | Table-driven tests | Multiple inputs/outputs | []struct with test cases | | Subtests | Related test variants | t.Run() for each case | | TestMain | Global setup/teardown | func TestMain(m *testing.M) | | t.Cleanup | Per-test cleanup | Deferred cleanup function | | fakes/fuzzing | Random input testing | testing.F, f.Fuzz() | | Race detector | Concurrent code | go test -race | | Coverage | Ensuring thoroughness | go test -cover |

What Do You Need?

  1. Test structure - Table-driven, subtests, organization
  2. Mocking - Fakes, interfaces, test doubles
  3. Concurrency testing - Race detector, parallel tests
  4. Coverage - Measuring and improving test coverage
  5. Test data - Fixtures, golden files, test helpers

Specify a number or describe your testing scenario.

Routing

| Response | Reference to Read | |----------|-------------------| | 1, "table", "driven", "multiple cases" | table-driven.md | | 2, "mock", "fake", "interface" | mocking.md | | 3, "race", "concurrent", "parallel" | concurrency.md | | 4, "coverage", "measure", "thorough" | coverage.md | | 5, general testing | Read relevant references |

Critical Rules

  • Table-driven for variations: Use for multiple inputs/outputs
  • Descriptive test names: TestFunctionName_State format
  • t.Cleanup for cleanup: Prefer over defer in tests
  • Run with -race: Must pass for concurrent code
  • Avoid mocking when possible: Use real implementations or fakes
  • Tests should fail for the right reason: Not due to flakiness

Test Template

func TestFunctionName(t *testing.T) {
    tests := []struct {
        name    string
        input   InputType
        want    WantType
        wantErr bool
        errIs   error
    }{
        {
            name:    "successful case",
            input:   InputType{...},
            want:    WantType{...},
            wantErr: false,
        },
        {
            name:    "validation error",
            input:   InputType{...},
            wantErr: true,
            errIs:   ErrValidation,
        },
    }

    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            got, err := FunctionName(tt.input)
            if (err != nil) != tt.wantErr {
                t.Errorf("FunctionName() error = %v, wantErr %v", err, tt.wantErr)
                return
            }
            if tt.errIs != nil && !errors.Is(err, tt.errIs) {
                t.Errorf("FunctionName() error = %v, wantIs %v", err, tt.errIs)
            }
            if !reflect.DeepEqual(got, tt.want) {
                t.Errorf("FunctionName() = %v, want %v", got, tt.want)
            }
        })
    }
}

Test Organization

project/
├── internal/
│   ├── service/
│   │   ├── service.go
│   │   ├── service_test.go
│   │   └── service_golden_test.go
│   └── service/
│       ├── mocks/          # Generated mocks (if needed)
│       └── testdata/       # Golden files, fixtures
└── testutil/
    ├── setup.go           # Test helpers
    └── fixtures.go        # Shared test data

Common Testing Patterns

HTTP Handlers

func TestHandler(t *testing.T) {
    tests := []struct {
        name       string
        method     string
        body       string
        wantStatus int
        wantBody   string
    }{
        {"valid POST", "POST", `{"foo":"bar"}`, 200, `{"result":"ok"}`},
        {"invalid JSON", "POST", `{`, 400, ""},
    }

    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            req := httptest.NewRequest(tt.method, "/test", strings.NewReader(tt.body))
            rec := httptest.NewRecorder()

            Handler(rec, req)

            if rec.Code != tt.wantStatus {
                t.Errorf("status = %d, want %d", rec.Code, tt.wantStatus)
            }
        })
    }
}

Using t.Cleanup

func TestWithCleanup(t *testing.T) {
    // Setup
    db := openTestDB(t)
    t.Cleanup(func() {
        db.Close()  // Runs even if test fails
    })

    // Test code...
}

Race Detection

# Run tests with race detector
go test -race ./...

# Run specific test with race detector
go test -race -run TestConcurrentFunction

Reference Index

| File | Topics | |------|--------| | table-driven.md | Table structure, subtests, naming | | mocking.md | Interfaces, fakes, mocking libraries | | concurrency.md | Race detector, parallel tests, sync | | coverage.md | -cover, -coverprofile, thresholds |

Success Criteria

Tests are good when:

  • Table-driven tests cover variations
  • Race detector passes (-race)
  • Coverage is meaningful (not just high numbers)
  • Tests are readable and maintainable
  • t.Cleanup used for resource cleanup
  • Test failures are clear about what went wrong