mockery

SKILL.md

Mockery Mock Generation Workflow

When to Activate This Skill

  • A new interface is added to internal/application/ports/
  • A test file contains a manually-written mock/stub for a ports interface
  • A developer asks to add mock coverage for an existing ports interface
  • A test needs to replace On(...).Return(...) setup on a hand-rolled struct with generated mock expectations

Placement Convention

Mocks live in the infrastructure layer, beside the concrete implementations:

Interface package (application/ports/) Mock output directory (infrastructure/)
ports/api/btc/ infrastructure/api/btc/mocks/
ports/api/eth/ infrastructure/api/eth/mocks/
ports/api/xrp/ infrastructure/api/xrp/mocks/
ports/file/ infrastructure/storage/file/transaction/mocks/
ports/repository/cold/ infrastructure/repository/cold/mocks/
ports/repository/watch/ infrastructure/repository/watch/mocks/

Pattern: ports/<layer>/<pkg>/infrastructure/<layer>/<pkg>/mocks/

Step-by-Step Workflow

  1. Locate the interface — find the Go file in internal/application/ports/ that defines the interface.
  2. Determine the target directory — use the placement convention table above to identify the dir value.
  3. Add or update .mockery.yaml — append the interface name under its package entry (or add a new package entry if none exists). See the template below.
  4. Run make mockery — regenerates all mocks in one pass. Never create or edit mock files manually.
  5. Verify — run make go-lint and make check-build to confirm the generated code compiles cleanly.
  6. Update tests — replace any manual stub usage with NewMock*(t) + .EXPECT() builder calls.

.mockery.yaml Entry Template

Adding an interface to an existing package entry

packages:
  github.com/hiromaily/go-crypto-wallet/internal/application/ports/<layer>/<pkg>:
    config:
      dir: "internal/infrastructure/<layer>/<pkg>/mocks"
      pkgname: "mocks"
    interfaces:
      ExistingInterface:
      MyNewInterface: # ← append here

Adding a brand-new package entry

github.com/hiromaily/go-crypto-wallet/internal/application/ports/<layer>/<pkg>:
  config:
    dir: "internal/infrastructure/<layer>/<pkg>/mocks"
    pkgname: "mocks"
  interfaces:
    MyNewInterface:

The global settings (filename, structname, template, formatter) are already configured in .mockery.yaml and apply automatically — do not override them per-package.

Generated File Conventions

Setting Value
Filename mock_<interface_name_snakecase>.go
Struct name Mock<InterfaceName>
Constructor NewMock<InterfaceName>(t)
Package mocks
Header // Code generated by mockery; DO NOT EDIT.

Test Usage — Before / After

Before (manual stub — do NOT write this)

// ❌ Manually-written stub in a test file
type stubAccountRepo struct{}

func (s *stubAccountRepo) GetOneMaxID(accountType domainAccount.AccountType) (*domainKey.BTCAccountKey, error) {
    return &domainKey.BTCAccountKey{Index: 0}, nil
}

func TestFoo(t *testing.T) {
    repo := &stubAccountRepo{}
    uc := NewUseCase(repo)
    // ...
    // Manual assertion:
    // (no automatic call-count verification)
}

After (mockery-generated mock — always use this)

// ✅ Import the generated mocks package
import coldmocks "github.com/hiromaily/go-crypto-wallet/internal/infrastructure/repository/cold/mocks"

func TestFoo(t *testing.T) {
    repo := coldmocks.NewMockBTCAccountKeyRepositorier(t) // registers cleanup automatically
    repo.EXPECT().
        GetOneMaxID(domainAccount.AccountTypeDeposit).
        Return(&domainKey.BTCAccountKey{Index: 0}, nil)

    uc := NewUseCase(repo)
    // ...
    // AssertExpectations is called automatically via t.Cleanup — do NOT call it manually
}

Key differences:

  • NewMock*(t) registers t.Cleanup(func() { mock.AssertExpectations(t) }) automatically — never call AssertExpectations manually.
  • .EXPECT().Method(args...).Return(values...) provides type-safe, call-count-verified expectations.
  • The generated mock handles all type assertions internally — no args.Get(0).(*Type) boilerplate.

Exceptions — Do NOT Add to .mockery.yaml

Type Reason
Ethereumer (ETH) DI-layer-only monolithic interface; not consumed by use cases
ETHTransactionSender Type alias for TxSender; mockery cannot generate mocks for type aliases — use TxSender mock instead
Use case interfaces (internal/application/usecase/*/interfaces.go) These are not ports interfaces; simple struct stubs are acceptable for testing use case orchestration
Composed/aggregate interfaces (Bitcoiner, ETHKeygenSignClient, etc.) Leaf interface mocks satisfy all compositions — add only the leaf interfaces that use cases depend on
Compile-time conformance stubs in *_test.go under ports/ Intentional type-check helpers, not test doubles — leave them as-is

Verification Commands

make mockery        # Regenerate all mocks after .mockery.yaml changes
make go-lint        # Required: zero lint errors
make check-build    # Required: full build succeeds
make go-test         # Recommended: run tests for affected packages

Related Files

  • .mockery.yaml — SSOT for all mock generation configuration
  • .claude/rules/internal/mockery.md — project rules enforcing this convention
  • .claude/rules/internal/infrastructure-layer.md — infrastructure layer rules
  • .claude/rules/internal/application-layer.md — application layer rules
Weekly Installs
11
GitHub Stars
125
First Seen
14 days ago
Installed on
gemini-cli11
opencode11
codebuddy11
github-copilot11
codex11
kimi-cli11