Stage 7 · Master
Testing Strategy
Unit and Table-Driven Tests
The first Meridian unit tests target the code that already exists: libs/platform helpers and each service's small config loader.
Why Unit Tests Come First
Meridian is still in the scaffolding phase. Every service binary builds, starts, and exposes only /healthz. That sharply limits what can be tested today, but it does not eliminate unit-test value. The code that already exists in libs/platform and in each service's internal/config package defines cross-service conventions; those conventions should be pinned down before the first real handler depends on them.
| Layer | What exists now | Best test shape |
|---|---|---|
| libs/platform/config | config.Env fallback helper | Dense table-driven unit tests |
| libs/platform/logging | Level parsing and service-tagged slog logger | Unit tests over level inputs and logger output |
| libs/platform/errors / response | Platform Contract wire types | Unit tests over constructors and JSON shape |
| libs/platform/tenancy | X-Tenant-Id constant and context helpers | Unit tests for context round-trips |
| apps/*/internal/config | Per-service env mapping | Table-driven config-loader tests |
Real Unit-Test Targets Today
libs/platform/config/config_test.gocreateResponsibility: Proves that config.Env returns the environment value when set and the fallback when unset or empty.
Why now: Every service config loader depends on this helper. A bug here would cascade into all four services at startup.
Connects to: Covers libs/platform/config/config.go, the first shared helper imported by apps/gateway and the three domain services.
libs/platform/tenancy/tenancy_test.gocreateResponsibility: Asserts that WithTenantID stores the tenant UUID in context and TenantIDFromContext reads it back safely.
Why now: Tenant propagation is the shared vocabulary for the entire platform. The helper must be boring and correct.
Connects to: Pins down the X-Tenant-Id propagation model described in docs/architecture/services.md.
apps/gateway/internal/config/config_test.gocreateResponsibility: Exercises Load() against GATEWAY_PORT, LOG_LEVEL, and the three downstream service URLs.
Why now: The gateway is the only service that knows every downstream address. A bad default here breaks local development immediately.
Connects to: Covers apps/gateway/internal/config/config.go and the .env.example values documented in the reference repo.
Table-Driven Examples
Go's table-driven style fits Meridian's current codebase because the rules are small, repetitive, and mostly input/output oriented. config.Env, logging level parsing, errors.New().WithTraceID(), and response.OKWithMeta() are all better expressed as short test tables than as one-off bespoke tests. The structure keeps edge cases visible without turning simple packages into noisy test suites.
The current repo has no tenant resolver middleware, no CRUD handlers, and no repository interfaces yet. This chapter therefore keeps its concrete examples on the code that does exist, and treats future handler tests as patterns for later chapters rather than present-tense implementation.
Service Config Loaders
Each service's internal/config.Load() function is another strong unit-test target because it translates .env.example into process-local settings. The gateway loader is the richest case today because it maps GATEWAY_PORT plus the three downstream service URLs. identity-service, community-service, and billing-service have a smaller surface, but the pattern is identical and should stay identical as database DSNs, Redis settings, and Kafka settings are added later.
The Decision
- The first Meridian unit tests target libs/platform and internal/config because those are the packages that already contain reusable behavior.
- Table-driven tests are the default style for config, logging, response, error, and tenancy helpers because the behavior is compact and repetitive.
- No lesson example assumes business handlers, repositories, or domain entities that do not yet exist in the scaffold.
- Later service-specific tests build on these conventions rather than replacing them.
Mark this lesson complete to store local progress and unlock a cleaner resume path the next time you visit.