A Slice Is Testable
Create Order through Frontend → API → Business Logic → Database → Response can be tested on its own, before any other slice exists. That is what makes a slice a unit of progress rather than a unit of work — and the test must say what it does not prove as clearly as what it does.
The situation, the reflex, and why it stalls
Every lesson starts where being stuck starts: someone has a problem, and the first move that comes to mind feels like progress.
What does it mean for a slice to be independently testable, what does a passing slice actually establish, and what does it leave unknown?
The create-order slice works: a customer with a cart clicks "place order", an order row appears, a confirmation shows. You demonstrated it and the founder was pleased. Now you are being asked "so checkout is done?" and you know the honest answer is not yes, but you cannot say precisely why.
Say yes with caveats. The slice runs, the demo worked, the tests pass; the caveats — payment is faked, stock is not checked — are details to handle in the next sprint. Calling it done keeps the momentum.
The caveats are the feature. A checkout that does not take payment or check stock is a form that inserts a row; what it proves is that a row can be inserted, which the skeleton proved weeks ago.
- The caveats are the feature. A checkout that does not take payment or check stock is a form that inserts a row; what it proves is that a row can be inserted, which the skeleton proved weeks ago.
- "Done" removes the slice from the plan, so the things it did not prove — decline handling, the race for the last unit — have no parent and are rediscovered as bugs.
- The demo's success is remembered and its stubs are forgotten. The fake payment is never replaced because nothing on the board says it is fake.
The move
Precisely enough to apply it to a problem you have never seen — not a slogan.
- Test the slice on its own terms: drive it from the outside — a request, or a click — through every layer it owns, and assert on the observable outcome: the row, the response, the page. Everything outside the slice is stubbed, and the stubs are named in the test.
- Write what a passing test proves as a list, and what it does not prove as a second list. The second list is not a confession; it is the definition of the next slices. "Does not prove: payment succeeds; stock is decremented; a second click does not create a second order."
- Distinguish independent from complete. A slice is independently testable when its test does not need other slices to exist. It is complete only when everything on its doesNotProve list has become a slice with its own test (What Cannot Be Simplified).
- Let the invariants say what the tests must assert. "Exactly one order per checkout" is an invariant; the slice test asserts it; the doesNotProve list says under which conditions (double click, retry after timeout) it is not yet asserted (Invariants as Tests).
Create Order, as a slice with both lists
The slice from the guide, with every layer named and the two required lines at the bottom. The doesNotProve line is deliberately as concrete as the proves line; each clause is a next slice.
- FrontendThe cart page's "place order" posts the cart id to
/checkoutand renders the confirmation or an error. - API
POST /checkoutvalidates the request shape and calls the checkout logic. - Business logicLoads the cart, captures current prices, creates the order in one transaction, asks the (stubbed) payment for a charge, empties the cart.
- DatabaseInserts one orders row and its lines; deletes the cart lines; all in the same transaction.
- ResponseThe order number and total, back to the page.
The test, driven from the entry point
The shape of a slice test: an entry-point request, named stubs, assertions on the observable outcome. What to notice is what is *not* mocked — the logic and the database are real — and the comment block that carries the second list.
1// Stubs (named, so the file says what is not real):2// payment: fake, always succeeds3// stock: not consulted4test('create order from a cart', async () => {5 const cart = await seedCart([{ product: 'p1', qty: 2 }, { product: 'p2', qty: 1 }])6 7 const res = await post('/checkout', { cartId: cart.id })8 9 expect(res.status).toBe(200)10 expect(res.body.orderNumber).toBeDefined()11 12 const orders = await db.orders.where({ cartId: cart.id })13 expect(orders).toHaveLength(1) // exactly one order14 expect(orders[0].lines.map(l => l.priceAtOrder)) // prices captured15 .toEqual(await currentPrices(['p1', 'p2']))16 expect(await db.cartLines.where({ cartId: cart.id })).toHaveLength(0)17})18// Does not prove: real payment; decline; stock; double submit; timeout.19// Each line above is a leaf under Checkout with its own test.The assertions are on the row and the response, not on which functions were called. The trailing comment is the doesNotProve list where it will be seen — beside the test that would otherwise look complete.
Proves, does not prove, and who cares
The matrix makes the two lists comparable and adds the column that decides priority: who is hurt if the unproven thing is false. It is what turns the doesNotProve list into an order of next slices.
| Claim | Status after the slice passes | If false, who is hurt | Next slice |
|---|---|---|---|
| One order per normal checkout | Proven | — | — |
| Prices captured on the order | Proven | — | — |
| Payment actually succeeds | Not proven (stub) | The store: orders exist that were never paid | Real payment leaf, then decline |
| Stock is checked and decremented | Not proven (skipped) | The customer: told "placed" for a sold-out item | Validate items; decrement on paid |
| Double submit yields one order | Not proven | The customer: charged twice | Idempotent checkout by cart id |
| Timeout leaves a recoverable state | Not proven | Both: nobody knows whether the order exists | Pending state and the no-answer leaf |
How to do it
Most important first.
- Drive the test from the slice's entry point — an HTTP request or a page action — not from an internal function. The slice is the path; test the path.
- Assert on what the customer or admin would see, plus the row. If the assertion is about an internal call, you are testing the layer, not the slice.
- Name every stub in the test — a fake payment that always succeeds, an in-memory cart — so the test file itself lists what is not real.
- Write the doesNotProve list next to the test and turn each line into a leaf in the tree, under this slice (Failure Modeling).
- Answer "is it done?" with both lists.
Worked on a concrete problem
The move has to produce something. This is what it produced.
- The create-order slice's test: given a cart with two lines, post to
/checkout; assert the response is a confirmation with an order number; assert exactly one order row exists with those two lines and the captured prices; assert the cart is now empty. Stubs named in the test: payment is a fake that returns success; stock is not consulted. It runs with no other slice present. - What it proves: the path from request to row to response works; prices are captured on the order; the cart is consumed. What it does not prove: that a real payment succeeds or that a decline is handled; that stock is checked or decremented; that a double submit creates one order, not two; that a timeout leaves a recoverable state. Four lines, four next slices.
- The honest answer to "is checkout done?": "The order path works and is tested. Checkout is done when these four things are also true, and here they are with an estimate each." The founder got a list instead of a yes, and the list was the plan.
How you know it worked
What now exists that did not before, and what question you can now ask.
- The slice has a test that runs alone, driven from its entry point, asserting on observable outcomes.
- The test names its stubs, and the doesNotProve list exists beside it.
- Every item on the list has become a leaf in the tree with its own "works when".
The questions you can now ask
The field this whole domain exists for. After this lesson, these are the questions to put to an unfamiliar problem.
- ?Can I drive this slice from its entry point with everything outside it stubbed — and are the stubs named?
- ?What does a passing test prove, in the customer's words?
- ?What does it not prove — and has each line become a slice with its own test?
- ?When someone asks "is it done?", can I answer with both lists?
What can go wrong
- A test that mocks the slice's own layers — the handler mocks the logic, the logic mocks the database — and passes while the real path is broken. The slice is the path; mock only what is outside it.
- A doesNotProve list that is written once and never becomes leaves, so it is a caveat rather than a plan.
- Treating "independently testable" as "no shared model": the slice test uses the real products table; independence is about other slices' behaviour, not shared data.
- A path test through real layers is slower and more infrastructure-dependent than a unit test of one layer; a slice test suite needs a database it can reset.
- The doesNotProve list makes every demo end with "and here is what is not real", which is honest and deflating in equal measure.
- "Independently testable means unit tested." A slice test is closer to an integration test of one path; unit tests of the layers inside it are additional, not substitutes (What a Unit Is and Where a Test Must Be Real in Design).
- "If the slice passes, the feature works." The slice passes under its stubs. It proves the path; the feature is the path plus everything on the second list.
Where this applies
Problem-solving advice is stated as universal far more often than it is. These labels say what each method is specific to — and where CONTESTED appears, the note gives the strongest form of the opposing view.
- GENERALDriving a path from its entry point and asserting on its observable outcome, with everything outside it stubbed and named, is how any slice in any layered system is tested.
- STAGE-SPECIFICIn a prototype, the doesNotProve list can stay a list; in production, each line must become a tested slice before the feature is declared done, and the difference is exactly what separates a prototype from a product (Prototype vs Production).
- ILLUSTRATIVEThe two-line cart, the four next slices and the founder's question are invented for the running example.
Where the depth lives
This domain asks the question and hands the answer off by name.
- — Testing & Reliability has no domain yet; the slice test here is the entry-point-driven, real-layers, named-stubs shape that a future test-strategy lesson would make its first example.