본문으로 건너뛰기
cccskills
무료GitHub에 공개

mockery-suite-tests

Use when writing Go tests that need test doubles or cover multiple cases - detect the repo's mocking approach (Mockery v3, gomock, hand-written testify mocks, fakes), table-driven cases, testify suites when the repo uses them, and real-Postgres tests for repository adapters

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

연동 도구PostgreSQL
설치 방법 보기

포함된 파일(1)

  • SKILL.md6.1 KB

SKILL.md(원문)

설치하기 전에 에이전트에게 전달되는 지시 내용을 확인할 수 있습니다.

Go Test Doubles & Table Tests

Overview

Table-driven tests and real-Postgres repository-adapter tests are non-negotiable everywhere. How test doubles are produced is not: follow the repository's own convention (rule repo-conventions-win) rather than importing Mockery into a repo that hand-writes testify mocks or uses gomock.

Core principle: multi-case behaviour is a table; a repository adapter test runs against real Postgres, never a mocked driver; mocks exist only at port boundaries.

Detect the repo's mocking approach first

ls .mockery.y*ml 2>/dev/null
grep -rlE 'Code generated by mockery|go.uber.org/mock' --include='*.go' . | head -3
  • .mockery.yml or files headed "Code generated by mockery" → Mockery v3: regenerate, don't hand-edit (see below).
  • go.uber.org/mock imports → gomock: regenerate with mockgen per the repo's go:generate directive, don't hand-edit.
  • Neither, but a test file defines type mockFoo struct implementing a port → the repo hand-writes testify mocks (or fakes). Follow that style; do not introduce a generator the repo doesn't already have. TaskTrooper's own server does exactly this (server/internal/port/mocks/llm_client.go).

Mockery v3 (when the repo already uses it)

  • Mocks come from the port interfaces, generated by Mockery v3. Names and location come from .mockery.yml; v3's defaults are Mock<Interface> in mocks_test.go inside the source package, constructor NewMock<Interface>(t) — don't assume a different naming scheme without checking the config.
  • Regenerate with the repo's own command (mockery, go generate ./..., or make mocks) — never hand-edit a generated mock.
  • Install Mockery from a pinned release binary (GitHub Releases, any OS) or brew install mockery on macOS; the project's own docs say not to go install mockery@latest.
  • A v2 .mockery.yaml migrates with mockery migrate; don't hand-port the config.
  • v3 generates an expecter API — use the typed EXPECT() methods, not raw On("MethodName", ...) string calls, so a renamed method breaks compilation instead of silently passing.
repo := mocks.NewMockTaskRepository(t)        // generated constructor, auto-asserts on cleanup
repo.EXPECT().Create(mock.Anything, task).Return(nil).Once()
svc := NewTaskService(repo)

Table-driven tests (every repo, every approach)

Every function with more than one case is a table. One row = one behavior with a descriptive name.

func TestTaskService_Create(t *testing.T) {
    tests := []struct {
        name    string
        title   string
        setup   func(*mockTaskRepository)
        wantErr error
    }{
        {name: "valid title persists",
            title: "Report",
            setup: func(r *mockTaskRepository) { r.onCreate(nil) }},
        {name: "blank title rejected before DB",
            title: "  ",
            setup: func(r *mockTaskRepository) { /* no expectation: repo must NOT be called */ },
            wantErr: domain.ErrInvalidTitle},
    }
    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            repo := newMockTaskRepository(t)
            tt.setup(repo)
            _, err := NewTaskService(repo).Create(t.Context(), tt.title)
            if !errors.Is(err, tt.wantErr) {
                t.Fatalf("got %v, want %v", err, tt.wantErr)
            }
        })
    }
}

Cover success, every error path, and edges (empty, not-found, conflict) as rows. A blank-title row that sets NO expectation proves the repo is never touched on invalid input. Use a testify suite (suite.Suite + SetupTest) only where the repo already uses one for shared fixtures — suites don't support t.Parallel(), so don't introduce one into a repo that parallelizes its tests.

Repository adapter tests run against real Postgres

A repository adapter test (the thing implementing the port against pgx/database/sql) is never unit-tested against a mocked driver — it proves nothing about the actual SQL. Use the repo's own test-database harness, or testcontainers-go modules/postgres with the repo's migrations applied, and assert on rows actually read back.

Modern Go testing

  • t.Context() (Go ≥1.24) instead of context.Background() in tests — it's canceled on test cleanup.
  • testing/synctest (Go ≥1.25) for deterministic tests of timers, timeouts and goroutine scheduling instead of real time.Sleep.
  • go test -race -count=1 ./... for anything touching concurrency — run it, don't just suggest it.

Rules

  • Mock ONLY at port boundaries (repositories, external clients). Domain logic uses real domain values — no mocks.
  • Assert on behavior and returned values, not on internal call ordering — over-specified expectations break on every refactor. Use .Once()/.Times(n) (or the gomock/hand-written equivalent) only where the count is the behavior under test.
  • Keep the suite green and output pristine: a happy-path test that logs errors hides real failures.
  • Never add a mocking library, generator or config file to a repository that doesn't already have one just to follow this skill — that is scope the reviewer bounces (rule repo-conventions-win).

Common Mistakes

  • Introducing Mockery into a repo that hand-writes mocks or uses gomock.
  • Hand-editing a generated mock instead of regenerating it.
  • Raw .On("Create", ...) string calls on a v3 expecter-generated mock.
  • One giant test with if-branches per case → make it a table.
  • Mocking a domain value object → construct the real one.
  • A repository-adapter test that mocks the SQL driver instead of running against Postgres.

Red Flags

  • type mock... struct hand-written in a repo whose other tests use a generator.
  • A test still compiles after an interface method was renamed → check whether it should have failed to.
  • Rows share a mock instance and pass only in a specific order.

리뷰

아직 리뷰가 없습니다. 사용해 본 소감을 남겨 주세요.

댓글

로그인 없이 쓸 수 있습니다. 사용 후기, 질문, 작동하지 않은 환경 등을 편하게 남겨 주세요.

아직 댓글이 없습니다. 첫 댓글을 남겨 보세요.

보내면 바로 공개됩니다. 이메일 주소나 전화번호 같은 개인 정보는 쓰지 마세요. URL은 2개까지입니다.

0 / 1,000자

이 양식은 reCAPTCHA로 보호되며 Google 개인정보처리방침 및 서비스 약관이 적용됩니다.

같은 저장소의 스킬

Use when writing acceptance criteria for a task - express each as an observable Given/When/Then that QA can execute, including negative cases

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

makifbaysal/tasktrooper1172026년 10월 20일 업데이트

Use when the diff adds or changes an endpoint, resolver, RPC, job or query that takes an object id, a role check, a request binding or a tenant filter - BOLA/IDOR, function-level authorization, mass assignment and tenant scoping

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

makifbaysal/tasktrooper1172026년 10월 20일 업데이트

Use on every UI change - semantic HTML, labels for controls, keyboard-navigable dialogs/menus, visible focus, and never color as the only signal

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

makifbaysal/tasktrooper1172026년 10월 20일 업데이트

Use when a task changes any screen, form, dialog, menu or control - Lighthouse/axe scan of the changed screens, a keyboard walk, and the thresholds that fail a task

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

makifbaysal/tasktrooper1172026년 10월 20일 업데이트

How to work a task returned with review, QA or UAT findings. Use when a task is in need_revision or PR review comments are in your context.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

makifbaysal/tasktrooper1172026년 10월 20일 업데이트

Use when the repository keeps its alerting in code, or a person asks for alerts outside TaskTrooper - PrometheusRule YAML, a Cloud Monitoring alert policy in Terraform, a Datadog monitor, each routed to TaskTrooper's alert ingest URL so it opens an incident here

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

makifbaysal/tasktrooper1172026년 10월 20일 업데이트

makifbaysal의 스킬 모두 보기

이 스킬의 문제 신고