Skip to content

feat: generate any double for an interface we own, and say where it lives - #96

Merged
retr0h merged 2 commits into
mainfrom
feat/specify-mock-layout
Aug 16, 2026
Merged

feat: generate any double for an interface we own, and say where it lives#96
retr0h merged 2 commits into
mainfrom
feat/specify-mock-layout

Conversation

@retr0h

@retr0h retr0h commented Aug 16, 2026

Copy link
Copy Markdown
Contributor

Two gaps found while converting mockRenderer, and a correction to the rule I wrote in #95.

1. The rule is now a bright line

#95 drew the line at whether a test asserts on the interaction — generate if it does, hand-write a stub if it doesn't.

That line is wrong in practice. It requires judgment at every call site, produces a different answer per author and per review, and leaves both forms in the codebase with no way to say which is right. Hand-rolling doubles for our own interfaces is not what we want, whatever the double happens to assert.

New rule: a double for an interface this organization defines is generated. Three exceptions, where generating buys nothing:

  • stdlib interfacesnet.Conn, fs.File, io.WriteCloser, slog.Handler. They don't move when our code does.
  • doubles carrying a real implementationmockPKISigner signs with a genuinely generated ed25519 key pair.
  • recorders for unjoinable goroutinescaptureStore records audit writes dispatched after the response is sent; a generated mock would assert a call count the test cannot pin down. Reason must be stated where it's defined.

The verbosity objection I raised in #95 has an answer I'd missed: a constructor returning a configured generated mock keeps call sites as short as the struct literals, while the thing satisfying the interface is still generated.

2. Where a generated mock lives

The corpus named the generator but never the layout, and the layout had drifted:

directory invocation
osapi (~35 sites), nats-client, nats-server <pkg>/mocks/ go tool …mockgen
gohai internal/executor/gen/ go run …mockgen
osapi-orchestrator none

In osapi, gen/ means oapi-codegen output — ~25 directories of it. In gohai it means mocks. The same directory name means two different things depending on which repository is open.

Order of work

  1. record the rule — this PR
  2. state it in all five Go repos' CONTRIBUTING.md, identically (task 5.4) — repo-standards requires a repo to carry what binds it
  3. fix the implementations (5.5–5.8)

No repository is asked to follow a rule it doesn't yet carry.

What changes in code, once this lands

gohai: four Collector doubles → generated mock + constructor; internal/executor/gen/mocks/. osapi-orchestrator: mockRenderer → generated MockRenderer. osapi: no change — both its doubles fall under exceptions.

just test — 12 passed, 0 failed.

🤖 Generated with Claude Code

The corpus named the generator but not the layout, and the layout had
drifted. About forty sites use <package>/mocks/ invoked through go tool.
gohai uses internal/executor/gen/ invoked through go run, and
osapi-orchestrator has no mocks package at all.

The harm is not untidiness. gen already means API-generator output in
about twenty-five osapi directories, so the same directory name means
two different things depending on which repository is open, and a reader
has to open it to find out which.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

Thank you for contributing to this project! 😊🕹️

The previous rule drew the line at whether a test asserts on the
interaction. That requires judgment at every call site, gives a
different answer per author and per review, and leaves both forms in the
codebase with no way to say which is right.

A bright line is worth more than the verbosity it costs: a double for an
interface this organization defines is generated. Three exceptions
remain, where generating buys nothing -- stdlib interfaces, doubles that
carry a real implementation, and recorders for goroutines a test cannot
join.

The verbosity has an answer. A constructor returning a configured
generated mock keeps call sites as short as the struct literals they
replace, while the thing satisfying the interface is still generated.

Also specifies where a generated mock lives. The corpus named the
generator but not the layout, and gen already means API-generator output
in about twenty-five osapi directories.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@retr0h retr0h changed the title feat: specify where a generated mock lives feat: generate any double for an interface we own, and say where it lives Aug 16, 2026
@retr0h
retr0h merged commit 2696b8d into main Aug 16, 2026
6 checks passed
@retr0h
retr0h deleted the feat/specify-mock-layout branch August 16, 2026 17:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant