feat: generate any double for an interface we own, and say where it lives - #96
Merged
Conversation
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>
|
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>
This was referenced Aug 16, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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:
net.Conn,fs.File,io.WriteCloser,slog.Handler. They don't move when our code does.mockPKISignersigns with a genuinely generated ed25519 key pair.captureStorerecords 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:
osapi(~35 sites),nats-client,nats-server<pkg>/mocks/go tool …mockgengohaiinternal/executor/gen/go run…mockgenosapi-orchestratorIn
osapi,gen/means oapi-codegen output — ~25 directories of it. Ingohaiit means mocks. The same directory name means two different things depending on which repository is open.Order of work
CONTRIBUTING.md, identically (task 5.4) —repo-standardsrequires a repo to carry what binds itNo repository is asked to follow a rule it doesn't yet carry.
What changes in code, once this lands
gohai: fourCollectordoubles → generated mock + constructor;internal/executor/gen/→mocks/.osapi-orchestrator:mockRenderer→ generatedMockRenderer.osapi: no change — both its doubles fall under exceptions.just test— 12 passed, 0 failed.🤖 Generated with Claude Code