Skip to content
cccskills
FreePublished on 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

The English summary is being prepared. Showing the original description.

Works withPostgreSQL
See how to install

Files included (1)

  • SKILL.md6.1 KB

SKILL.md (original)

Review the instructions the agent will receive before you install.

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.

Reviews

No reviews yet. Tell others what you thought.

Comments

No login needed. Share how it worked for you, ask a question, or note where it did not run.

No comments yet. Be the first to write one.

Comments are published right away. Do not include personal information such as email addresses or phone numbers. Up to 2 URLs.

0 / 1,000 characters

This form is protected by reCAPTCHA, and the Google Privacy Policy and Terms of Service apply.

More skills from this repository

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

The English summary is being prepared. Showing the original description.

makifbaysal/tasktrooper117Updated Oct 21, 2026

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

The English summary is being prepared. Showing the original description.

makifbaysal/tasktrooper117Updated Oct 21, 2026

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

The English summary is being prepared. Showing the original description.

makifbaysal/tasktrooper117Updated Oct 21, 2026

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

The English summary is being prepared. Showing the original description.

makifbaysal/tasktrooper117Updated Oct 21, 2026

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.

The English summary is being prepared. Showing the original description.

makifbaysal/tasktrooper117Updated Oct 21, 2026

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

The English summary is being prepared. Showing the original description.

makifbaysal/tasktrooper117Updated Oct 21, 2026

See all skills by makifbaysal

Report a problem with this skill