SlicesGENERALSTAGE-SPECIFICILLUSTRATIVE

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 moveWorked exampleNext questions

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.

The question

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 situation

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.

The reflex

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.

Why it stalls

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.

What the reflex produces — and fails to produce
  • 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.
ProblemUnderstandRequirementsConstraintsUnknownsDecompositionSmallest StepModelExperimentObserveDebugLearnIterate

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.

Create Order
A customer with a cart places an order and sees a confirmation
  1. FrontendThe cart page's "place order" posts the cart id to /checkout and renders the confirmation or an error.
  2. APIPOST /checkout validates the request shape and calls the checkout logic.
  3. Business logicLoads the cart, captures current prices, creates the order in one transaction, asks the (stubbed) payment for a charge, empties the cart.
  4. DatabaseInserts one orders row and its lines; deletes the cart lines; all in the same transaction.
  5. ResponseThe order number and total, back to the page.
proves
The path from click to row to confirmation works; exactly one order exists per checkout under normal conditions; prices are captured on the order; the cart is consumed.
does not prove
That a real payment succeeds or a decline is handled; that stock is checked or decremented; that a double submit or a retry after timeout still yields exactly one order; that a browser closed mid-checkout leaves a recoverable state.

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.

A slice test for Create Order
1// Stubs (named, so the file says what is not real):
2// payment: fake, always succeeds
3// stock: not consulted
4test('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 order
14 expect(orders[0].lines.map(l => l.priceAtOrder)) // prices captured
15 .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.

ClaimStatus after the slice passesIf false, who is hurtNext slice
One order per normal checkoutProven
Prices captured on the orderProven
Payment actually succeedsNot proven (stub)The store: orders exist that were never paidReal payment leaf, then decline
Stock is checked and decrementedNot proven (skipped)The customer: told "placed" for a sold-out itemValidate items; decrement on paid
Double submit yields one orderNot provenThe customer: charged twiceIdempotent checkout by cart id
Timeout leaves a recoverable stateNot provenBoth: nobody knows whether the order existsPending 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.

Next questions
  • ?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

How the move itself fails
  • 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.
What the move costs
  • 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.
Misreads
  • "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.

Further
  • 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.