# ADR-011: TDD Foundation **Status:** Accepted **Date:** 2026-05-05 **Supersedes:** none ## Context The vertical-feature monorepo refactor (ADRs 001-010) established clean architecture with per-feature DI containers but did not enforce TDD as the path of least resistance. Agentic workers were producing code-first commits with tests added later, leading to test theatre and mock/real drift. ## Decision 1. **New package `@repo/core-testing` (tag: tooling)** — shared test utilities: defineFactory, defineContractSuite, renderWithProviders, mock-payload helpers, jsdom setup file. Tagged `tooling` so any package may depend on it as devDependency without boundary violation. 2. **Vitest base configs split into node + jsdom** with safety defaults (clearMocks, restoreMocks, mockReset, unstubGlobals, sequence.shuffle) and coverage thresholds (80/75/80/80 baseline; 100% in entities + use-cases + controllers). 3. **Factories per feature** in `src/__factories__/` replace inline fixtures. Stable date defaults (2026-01-01) so snapshot diffs reflect SUT behavior only. 4. **Contract suites per repository interface** in `src/__contracts__/` run against every implementation (Mock + Payload). Eliminates the class of bug where the mock and the real impl drift apart. 5. **Tests in core-\* packages and apps** — composition smoke tests (appRouter, payloadConfig, bind-production, providers). 6. **Storybook test-runner** — every story executed as a smoke test. 7. **CI workflow** — typecheck + lint + boundaries + test + build + e2e + storybook on every PR. Coverage uploaded as artifact. 8. **Two new docs** — tdd-workflow.md (process) + restructured adding-a-feature.md (interleaves tests with impl). ## Alternatives considered - **Fixture files instead of factories** — rejected. Fixtures rot with schema changes and require manual updates per test. - **One shared test file per impl** — rejected. Contract suites give the same coverage in fewer LOC and prevent drift. - **Real Postgres in tests via testcontainers** — rejected for unit tests (slow, complex). Repository contract suites + vi.mock('payload') give equivalent confidence in milliseconds. - **Stryker mutation testing** — deferred. Coverage thresholds + contract suites get us most of the way; mutation testing is incremental. ## Consequences - New package to maintain (small, mostly stable surface). - Coverage thresholds may fail builds initially; we add tests to cross threshold as features land. - Sequence shuffle may surface latent flakes; we fix as found. - Templates for new features now require writing tests first; this is by design. ## Refines - ADR-006 (boundary tags) — adds @repo/core-testing as a tooling package.