Stage 7 · Master
Testing Strategy
Mocking Interfaces for Tests
Meridian defaults to small hand-written fakes, and introduces interfaces only at real boundaries such as gateways, payment providers, and repositories.
Why Mocks Are Not the Starting Point
The current Meridian scaffold is mostly concrete functions. libs/platform/config.Env, logging.New, errors.New, response.OK, and tenancy.WithTenantID are direct helpers, not interfaces. That is a good starting point. Interfaces should not be created merely to make a mocking framework happy; they should appear only where the code crosses a genuine boundary such as a database, a downstream HTTP client, or a payment provider webhook verifier.
No generated mocks belong in the current scaffold because there are no repository interfaces, payment clients, or tenant-resolver abstractions yet. The present lesson defines the rule for later chapters rather than describing a mock-heavy codebase that already exists.
Real Boundaries Worth Abstracting
| Future seam | Why an interface is justified | Preferred test double |
|---|---|---|
| gateway tenant resolver | Slug-to-tenant UUID lookup may call identity-service or a repository | Hand-written fake resolver |
| billing-service payment gateway client | Webhook verification and provider API calls are external I/O | Hand-written fake or narrow generated mock |
| service repositories | SQL persistence is an external dependency and a source of failures | Fake for unit tests, Testcontainers for integration tests |
| Kafka publisher / consumer adapter | Networked message transport needs isolation in unit tests | Small fake producer or consumer |
These are concrete, domain-shaped seams. A tenant resolver belongs to the gateway because only the gateway translates a human slug into the UUID later carried as X-Tenant-Id. A payment provider client belongs to billing-service because invoice and payment workflows are its bounded context. Those seams are test-worthy precisely because they isolate external systems from business rules.
Hand-Written Fakes First
The default Meridian testing double is a small fake kept close to the test file. It returns controlled data and records the minimum input needed for assertions. That style keeps unit tests readable and makes tenant-aware invariants explicit. For example, a billing-service application service that enqueues recurring invoice work should be able to assert which tenant_id, building_id, or billing period was handed to the queue without involving code generation or brittle call-order expectations.
When Generated Mocks Earn Their Keep
Generated mocks are still acceptable when the interaction pattern is itself the contract. A future outbox dispatcher might need to publish an event only after a transaction commits; a payment orchestration flow might need to verify a webhook, persist the payment, then emit InvoicePaid. In those narrower cases, expectation tooling can be worth the ceremony. The rule is selective adoption: generate only for seams whose complexity is real, shared, and otherwise painful to express.
The Decision
- Do not invent interfaces around libs/platform helpers just to enable mocking; they are already small, concrete functions.
- Introduce interfaces only at real boundaries: repositories, provider clients, message adapters, or gateway lookup seams.
- Default to hand-written fakes that record tenant-aware inputs locally in the test file.
- Use generated mocks only where interaction ordering is the behavior under test, not merely an implementation detail.
Mark this lesson complete to store local progress and unlock a cleaner resume path the next time you visit.