agentleFS
hiromailyhiromaily/go-crypto-wallet/.cursor/rules/go/go-test.mdc

go-crypto-wallet / go

Cursor rule128 starsChanged 6 months agoMITView on GitHub

Go Test Rules

We split this go-test.mdc into the rules it gives an agent

8 rules. About 0 are common, 0 are distinctive, and 8 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈0

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 8

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    Always use testify. Never use t.Errorf, t.Fatalf, or reflect.DeepEqual directly.

    line 40
  2. Very specific:

    Rule: Use require when subsequent test code depends on the assertion passing.

    line 47
  3. Very specific:

    Always use t.Run with a slice of test cases:

    line 53
  4. Very specific:

    Always call t.Helper() in helper functions so failures point to the call site.

    line 101
  5. Very specific:

    Create a testutil/ subdirectory inside the package the utilities belong to.

    line 114
  6. Very specific:

    Add t.Parallel() to tests with no shared mutable state.

    line 141
  7. Very specific:

    When the code under test spawns multiple goroutines that all call the same mock method, using per-call .Once() expectations with different …

    line 147
  8. Very specific:

    Rule: When N goroutines call the same mock method, set up a single expectation with .Times(N) returning a value that satisfies ALL callers …

    line 151

≈ “Common” is an estimate: at least 1 in 5 of the 6,350 Cursor rule files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
go-test.mdc
---
description: Go Test Rules
globs: ["**/*test.go"]
alwaysApply: false
---


# Go Test Rules

## Overview

Rules for writing and modifying test files (`*_test.go`) in go-crypto-wallet.

**Full reference (SSOT)**: @docs/guidelines/testing.md

---

## Test File Organization

| Test type | Filename | Package | Build tag |
|-----------|----------|---------|-----------|
| Unit test | `*_test.go` | same package (e.g. `package btc`) | none |
| Integration test (DB / external) | `*_integration_test.go` | external (e.g. `package btc_test`) | `//go:build integration` |
| E2E / complex multi-service | `*_test.go` in `internal/integration_test/` | `package integration_test` | `//go:build integration` |

```go
// unit_test.go — same package
package btc

// repository_integration_test.go — external package + build tag
//go:build integration

package postgres_test
```

---

## Test Framework — Mandatory

Always use [testify](https://github.com/stretchr/testify). **Never** use `t.Errorf`, `t.Fatalf`, or `reflect.DeepEqual` directly.

| Package | When to use |
|---------|-------------|
| `require` | Fatal — test stops immediately on failure |
| `assert` | Non-fatal — test continues after failure |

**Rule**: Use `require` when subsequent test code depends on the assertion passing; use `assert` otherwise.

---

## Table-Driven Tests — Mandatory

Always use `t.Run` with a slice of test cases:

```go
func TestFoo(t *testing.T) {
    tests := []struct {
        name    string
        input   string
        wantErr bool
    }{
        {"valid", "ok", false},
        {"empty", "", true},
    }
    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            err := Foo(tt.input)
            if tt.wantErr {
                require.Error(t, err)
            } else {
                require.NoError(t, err)
            }
        })
    }
}
```

---

## Mock Usage — Mandatory

**Always use mockery-generated mocks. Never write mock structs by hand.**

See @.claude/rules/internal/mockery.md for placement convention and exceptions.

**Import alias convention**:

| Mocks package | Import alias |
|---------------|--------------|
| `infrastructure/api/btc/mocks` | `btcapiamocks` |
| `infrastructure/api/eth/mocks` | `ethapiamocks` |
| `infrastructure/api/xrp/mocks` | `xrpapiamocks` |
| `infrastructure/repository/cold/mocks` | `coldmocks` |
| `infrastructure/repository/watch/mocks` | `repomocks` |
| `infrastructure/storage/file/transaction/mocks` | `storagemocks` |

---

## Helper Functions

Always call `t.Helper()` in helper functions so failures point to the call site.

---

## Test Utilities (`testutil/`)

**Rule**: A global `pkg/testutil` package is **prohibited**. Co-locate test utilities with the package they support.

```
pkg/db/testutil/       ← helpers for pkg/db (e.g. OpenTestDB)
internal/foo/testutil/ ← helpers for internal/foo
```

Create a `testutil/` subdirectory inside the package the utilities belong to. Never create a standalone `pkg/testutil/` or `internal/testutil/` package.

---

## What NOT to Do

```go
// ❌ Standard library assertions — use testify
t.Errorf("got %v, want %v", got, expected)

// ❌ Hand-written mock structs — use mockery
type stubRepo struct{}

// ❌ Manual AssertExpectations — automatic via NewMock*(t)
mock.AssertExpectations(t)

// ❌ reflect.DeepEqual — use assert.Equal
if !reflect.DeepEqual(expected, got) { t.Error(...) }

// ❌ Global testutil package
import "github.com/hiromaily/go-crypto-wallet/pkg/testutil"
```

---

## Parallelism

Add `t.Parallel()` to tests with no shared mutable state. Do **not** add it to tests sharing DB connections.

---

## Concurrent Goroutines and Mock Expectations

When the code under test spawns multiple goroutines that all call the **same mock method**, using
per-call `.Once()` expectations with different return values is non-deterministic — goroutine A
may steal goroutine B's response, causing "called over N times" panics or unsatisfied expectations.

**Rule**: When N goroutines call the same mock method, set up a single expectation with `.Times(N)`
returning a value that satisfies ALL callers simultaneously.

```go
// ❌ BAD: two goroutines race; either may receive the wrong response
deps.ledgerPoller.EXPECT().LedgerCurrent(mock.Anything).Return(res1, nil).Once()
deps.ledgerPoller.EXPECT().LedgerCurrent(mock.Anything).Return(res2, nil).Once()

// ✅ GOOD: use Times(N) with a response value that satisfies every caller
// (e.g. use the max of all required ledger indices)
ledgerRes := &ResponseLedgerCurrent{}
ledgerRes.Result.LedgerCurrentIndex = maxRequiredIndex
deps.ledgerPoller.EXPECT().LedgerCurrent(mock.Anything).Return(ledgerRes, nil).Times(2)
```

This applies whenever use-case code fans out work into `go func()` goroutines and each goroutine
calls the same mock method (e.g. `LedgerCurrent`, `GetBalance`, `GetTransaction`).

---

## Verification

```bash
make go-lint   # Required: zero lint errors
make go-test   # Required: all unit tests pass
```

---

## Related Rules

- @.claude/rules/internal/mockery.md — mock generation placement convention
- @.claude/rules/go/conventions.md — Go coding conventions
- @docs/guidelines/testing.md — full testing guidelines (SSOT)

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

Reviews can't be read right now.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.