AgentStack
SKILL verified MIT Self-run

Go Testing

skill-els0r-skills-go-testing · by els0r

>

No reviews yet
0 installs
7 views
0.0% view→install

Install

$ agentstack add skill-els0r-skills-go-testing

✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access No
  • Filesystem access No
  • Shell / process execution No
  • Environment & secrets No
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

Are you the author of Go Testing? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

Go Testing

Every piece of logic must be tested.

Test Libraries

| Library | When to use | |---------|------------| | testify/require | Default — fails fast, clear messages | | testify/assert | Only when the test should continue after a failure | | google/go-cmp | Deep comparisons with custom cmp.Options | | gock | Mock HTTP calls — never hit real endpoints in tests | | testcontainers | Integration tests with external services (databases, queues) |

Favor require over assert. It fails immediately and prevents cascading noise.

Table-Driven Tests

The default pattern. Use for all new tests.

func TestValidateEmail(t *testing.T) {
    tests := []struct {
        name    string
        email   string
        want    bool
        wantErr string // use sentinel errors when possible (see go-style)
    }{
        {"valid email", "user@example.com", true, ""},
        {"empty email", "", false, "email cannot be empty"},
        {"invalid format", "not-an-email", false, "invalid email format"},
    }

    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            got, err := ValidateEmail(tt.email)
            if tt.wantErr != "" {
                require.ErrorContains(t, err, tt.wantErr)
                return
            }
            require.NoError(t, err)
            require.Equal(t, tt.want, got)
        })
    }
}

When testing sentinel errors, use require.ErrorIs(t, err, ErrNotFound) instead of string matching.

Test Helpers and Setup

  • Mark helpers with t.Helper() — no exceptions. Stack traces point to the calling test.
  • Use t.Cleanup(fn) for teardown instead of defer — it runs even if the test calls t.Fatal.
  • Arrange-act-assert structure. Every time.
func setupTestDB(t *testing.T) *DB {
    t.Helper()
    db, err := NewDB(":memory:")
    require.NoError(t, err)
    t.Cleanup(func() { db.Close() })
    return db
}

Parallel Tests

Use t.Parallel() for independent subtests to speed up suites.

for _, tt := range tests {
    t.Run(tt.name, func(t *testing.T) {
        t.Parallel()
        // ...
    })
}

Go 1.22+: loop variable is per-iteration scoped — tt := tt capture is no longer needed.

Mocking

Mock via interfaces. Define mocks in the test file, not production code. Keep them minimal.

type MockEmailSender struct {
    Sent []Email
    Err  error // set to simulate failure
}

func (m *MockEmailSender) Send(to, subject, body string) error {
    if m.Err != nil {
        return m.Err
    }
    m.Sent = append(m.Sent, Email{to, subject, body})
    return nil
}

Fuzzing

Use testing.F for input-discovery testing on parsers, validators, and serialization.

func FuzzParseConfig(f *testing.F) {
    f.Add([]byte(`{"key": "value"}`))
    f.Add([]byte(`{}`))

    f.Fuzz(func(t *testing.T, data []byte) {
        cfg, err := ParseConfig(data)
        if err != nil {
            return // invalid input is expected
        }
        // round-trip: marshal back and re-parse
        out, err := cfg.Marshal()
        require.NoError(t, err)
        require.Equal(t, cfg, mustParse(t, out))
    })
}

Test File Conventions

| Type | File Pattern | Notes | |------|-------------|-------| | Unit | *_test.go | Same package | | Integration | *_integration_test.go | Use //go:build integration build tag |

go test ./...                          # unit tests
go test -tags integration ./...        # integration tests
go test -count=1 ./...                 # bypass test cache

Benchmarks

Write benchmarks for performance-sensitive code. Use b.Loop() (Go 1.24+) — prevents dead-code elimination automatically.

func BenchmarkProcessData(b *testing.B) {
    data := generateTestPayload()
    b.ResetTimer()
    b.ReportAllocs()
    for b.Loop() {
        ProcessData(data)
    }
}

For benchstat comparisons, sync.Pool, and the full optimization workflow, see the go-performance skill.

Race Detector

Always run tests with -race. Non-negotiable for goroutines, shared state, or channels.

go test -race ./...

Skipping Slow Tests

Use testing.Short() to gate expensive tests in local dev. CI runs without -short.

func TestExpensiveIntegration(t *testing.T) {
    if testing.Short() {
        t.Skip("skipping in short mode")
    }
    // ...
}

Rules

  1. No test, no merge. Every exported function gets a test. Every bug fix gets a regression test.
  2. require over assert unless you explicitly need the test to continue.
  3. errors.Is over string matching — use sentinel errors (see go-style).
  4. No real endpoints in tests — mock HTTP with gock, databases with testcontainers or in-memory fakes.
  5. t.Helper() on every helper — no exceptions.
  6. -race in CI — always.
  7. Table-driven by default — flat tests only for genuinely one-off cases.

Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet — be the first.

Versions

  • v0.1.0 Imported from the upstream source.