Stage 7 · Master
Testing Fieldwork
Mocking Interfaces for Tests
Prefer small hand-written fakes until a generated mock earns its complexity by saving real maintenance pain.
Why I Do Not Start with Generated Mocks
Mocking in Go gets strange fast if every interface immediately generates a miniature framework around itself. I have worked in codebases where half the diff on a small feature was mock regeneration noise, expectation chains, and brittle interaction assertions that said more about the implementation than the behavior. I did not want Fieldwork to take that path by default.
The concrete reason is that most Fieldwork seams are small. A task service might depend on a repository with two methods. An auth handler might depend on a token issuer and one service method. For interfaces that size, a hand-written fake is usually clearer than generated code. It sits in the test file, shows exactly what behavior is being substituted, and can be changed in the same diff as the production code without a second tool step.
| Approach | Immediate benefit | What it costs later |
|---|---|---|
| Generate mocks for every interface | Fast setup once tooling exists | Brittle expectations and regeneration churn |
| Hand-written fakes for small seams | Readable tests and cheap edits | You write a little boilerplate yourself |
| No seams, use concrete dependencies | Fewer interfaces | Harder to isolate unit tests cleanly |
The Case for Hand-Written Fakes
A good fake does two things: it returns controlled outputs and it records the inputs that matter to the test. That is enough for most unit scenarios in Fieldwork. If a service should stamp tenant_id onto a new task before saving it, the fake repository can capture the task and let the test assert on the stored value. There is no need for a sprawling expectation DSL just to answer that.
Fakes also age better when implementations change. Suppose we refactor a service to combine two repository calls into one transaction helper. A generated mock with ordered expectations may fail because the call graph changed even if the external behavior stayed correct. A fake focused on inputs and outputs often survives that refactor because it was testing the contract, not the choreography.
If your fake is becoming painful to write, the problem may be the production interface, not the testing style. Wide interfaces are hard to fake because they are already doing too much.
When a Generated Mock Is Actually Worth It
Generated mocks are not forbidden in Fieldwork; they just have to earn their keep. They make sense when an interface is legitimately wide, reused across many tests, or when interaction shape is itself part of the contract. An outbound notifier that must call two collaborator methods in order with specific retries is a better candidate than a tiny repository used by one service.
Even then, I try to keep the scope narrow. Generate a mock for the hard seam, not for everything in sight. The failure mode of broad mock adoption is test code that mirrors production package structure one-to-one and breaks whenever internals shift. That is high maintenance disguised as rigor.
Keeping Test Doubles Honest
The danger with any test double is overfitting the test to a fantasy implementation. Fieldwork keeps doubles honest by making interfaces small, by pairing unit tests with integration tests for storage and transport layers, and by resisting overly clever fake behavior. A fake that re-implements half the repository logic is a bug farm. A fake should be boring and intentionally incomplete.
I also prefer colocating many fakes with the tests that use them instead of building a giant shared mocks package. Shared test helper packages can look tidy and slowly become a second framework nobody wants to touch. If a fake exists for one package's tests, it should usually live close to those tests and evolve with them.
- Reach for hand-written fakes first when the interface is small and the contract is obvious.
- Use generated mocks selectively when interaction ordering is itself the behavior under test.
- Treat painful fakes as a design smell in the production interface.
The Default Testing Double in Fieldwork
Fieldwork defaults to hand-written fakes for unit tests because most seams are small and the resulting tests read better. Generated mocks remain available, but only for the narrower cases where precise collaborator choreography matters more than simple input-output behavior.
That choice keeps the test suite closer to the actual domain and farther from mocking ceremony. The rejected alternative was generating everything up front and paying the maintenance bill forever. We chose the cheaper long-term path: explicit small seams, local fakes, and mock tooling only when the interface complexity genuinely justifies it.
Mark this lesson complete to store local progress and unlock a cleaner resume path the next time you visit.