docs(arch): rewrite §04 framing — drop 'mock is the surprising one'
User pushback: 'I already need mock data for dev and for testing at runtime' — i.e., the dual role of the mock is obvious, not surprising. Earlier framing was condescending. Reframe around 'runtime reach': - Section blurb leads with 'three artifacts at different distances from runtime' - Three-roles card intro: same neighborhood, different reach. Mock is reached by runtime; contract + factory only by tests. - 'The mock has two jobs' → 'The mock is reached from two directions — both legitimate, neither is the test version'. - Contract framing now leads with its real purpose: it tests the mock alongside the real impl so you can trust the mock as a runtime artifact.
This commit is contained in:
@@ -1529,14 +1529,15 @@ footer .colophon {
|
||||
<div class="section-num">§ 04</div>
|
||||
<div>
|
||||
<h2 class="section-title">Mocks, contracts & <em>factories</em>.</h2>
|
||||
<p class="section-blurb">Three things in every feature live near tests but play different roles. The <strong>mock repository</strong> is a real implementation of the repository interface — it's also the default DI binding. The <strong>contract</strong> is a portable test suite that runs against any implementation. The <strong>factory</strong> is a builder for valid entity values. The mock isn't <em>only</em> a test thing; that's the part that surprises people.</p>
|
||||
<p class="section-blurb">Three artifacts that sit near tests, at different distances from runtime. The <strong>mock repository</strong> is a real implementation of the interface — runtime code (DI, dev mode, storybook) reaches it. The <strong>contract</strong> is a test suite that runs against any implementation of the repo interface, mock or real. The <strong>factory</strong> builds valid entity values. The relationship between them is the interesting bit.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="cf-card three-roles-card">
|
||||
<div class="cf-tag">three roles · one test surface</div>
|
||||
<h3>Where each lives, and <em>why</em>.</h3>
|
||||
<p>The mock is the surprising one. People assume it lives in <code>__mocks__/</code> because tests use it — but the DI container needs it as the <em>default binding</em> at runtime, and reaching into <code>__mocks__/</code> from production code crosses a boundary. So the mock lives next to the real implementation, in <code>infrastructure/repositories/</code>. They're siblings.</p>
|
||||
<div class="cf-tag">where the boundaries are</div>
|
||||
<h3>Same neighborhood, <em>different reach</em>.</h3>
|
||||
<p>The mock <em>is</em> a test artifact, but it's also more than that — it's a real implementation of the repository interface, and runtime code reaches it directly. DI binds it as the default; dev mode runs against it when Payload isn't booted; storybook stories that need data resolve to it. The contract and factory are <em>only</em> reached from test files. That difference in reach is what determines where each lives.</p>
|
||||
<p>So the mock sits in <code>infrastructure/repositories/</code> next to the real impl — they're sibling implementations of the same interface, both legitimate citizens of the runtime layer. The contract and factory live under <code>__</code>-prefixed directories that nothing outside <code>*.test.ts</code> ever imports from.</p>
|
||||
|
||||
<div class="three-roles-diagram">
|
||||
<div class="role role-interface">
|
||||
@@ -1577,16 +1578,16 @@ footer .colophon {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p style="margin-top: 32px;"><strong>The mock has two jobs.</strong></p>
|
||||
<p style="margin-top: 32px;"><strong>The mock is reached from two directions.</strong> Both are legitimate, neither is "the test version":</p>
|
||||
<ol class="role-jobs">
|
||||
<li><strong>Default DI binding.</strong> <code>BlogModule</code> binds <code>IArticlesRepository</code> to <code>MockArticlesRepository</code> at module-load time. Anything resolving that symbol — use cases, controllers, the whole chain — gets the mock until <code>bindProductionBlog(config)</code> swaps it for the real Payload-backed one. See §03.</li>
|
||||
<li><strong>Test fake (direct injection).</strong> Unit tests skip the container entirely. They construct the mock with <code>new MockArticlesRepository()</code> and pass it directly into the use-case factory function. No DI involved — just a closure with a fake repo.</li>
|
||||
<li><strong>By the DI container at runtime.</strong> <code>BlogModule</code> binds <code>IArticlesRepository</code> to <code>MockArticlesRepository</code> at module-load time. Anything resolving that symbol — use cases, controllers, tRPC procedures, the dev server — gets the mock until <code>bindProductionBlog(config)</code> swaps it for the real Payload-backed one. See §03.</li>
|
||||
<li><strong>By tests, via direct construction.</strong> Unit tests skip the container entirely. They construct the mock with <code>new MockArticlesRepository()</code> and pass it directly into the use-case factory function. Same class, different consumer — just a closure with a fake repo.</li>
|
||||
</ol>
|
||||
|
||||
<p style="margin-top: 24px;"><strong>The contract and factory are pure test ergonomics</strong> — they only show up in <code>*.test.ts</code> files. Both live under <code>__</code>-prefixed directories that signal "test territory; not part of the public surface; not imported by runtime code." Their roles:</p>
|
||||
<p style="margin-top: 24px;"><strong>The contract and factory are reached from one direction only — tests.</strong> They never appear in runtime imports. Their roles:</p>
|
||||
<ul class="role-jobs">
|
||||
<li><strong>Contract</strong> = a single suite of <code>it()</code> blocks parameterized by <code>buildSubject</code>. Run it against the mock, run it against the real impl. If they diverge — your mock is lying to you and you'd never have caught it without the contract.</li>
|
||||
<li><strong>Factory</strong> = a sequence-counter builder. <code>articleFactory.build({ slug: "x" })</code> hands you a valid <code>Article</code> with sensible defaults; you only override the fields the test actually cares about. Used by the contract <em>and</em> by every use-case / controller test.</li>
|
||||
<li><strong>Contract</strong> = a single suite of <code>it()</code> blocks parameterized by <code>buildSubject</code>. Run it against the mock, run it against the real Payload-backed impl. If they diverge — your mock is lying about Payload's behavior, and you'd never catch it without the contract. This is its whole reason to exist: <em>it tests the mock so you can trust it</em>, alongside testing the real impl.</li>
|
||||
<li><strong>Factory</strong> = a sequence-counter builder. <code>articleFactory.build({ slug: "x" })</code> hands you a valid <code>Article</code> with sensible defaults; you only override the fields the test cares about. Used by the contract <em>and</em> by every use-case / controller test that needs entity values without writing 8 lines of inline fixtures.</li>
|
||||
</ul>
|
||||
|
||||
<details class="cf-detail">
|
||||
|
||||
Reference in New Issue
Block a user