You cannot write the assertion for a new feature's first test. What does that tell you, and what do you do?
answer
- Being stuck is the finding
- Something must say what correct means
- The gap is in the requirement
- Arrive with candidate answers, not questions
- Never let the implementation decide it
basics
~20 sAn assertion you cannot write means the requirement has no agreed oracle for that case yet. Treat it as a discovered ambiguity: stop, get a decision on the expected outcome from whoever owns the behaviour, then encode that decision as the assertion.
solid answer
~50 sThe blocked assertion is the most valuable signal test-first produces. You have arranged a concrete input and discovered that nobody, including you, can say what the correct output is — that is a requirements gap found before any code encodes a guess. The disciplined response is to name the specific undecided case, take it to whoever owns the behaviour with two or three candidate answers rather than an open question, and record the decision as the assertion plus a test name that states it. If a decision cannot be had immediately, keep moving on cases that are decided and leave the undecided one explicitly pending, so the gap stays visible instead of being silently resolved by whatever the implementation happens to do. Never invent an expected value to unblock yourself and let the code define correctness afterwards.
code
pseudocode · 12 lines# blocked: no agreed expected value for this case
test "duplicate reading with identical capture time":
outcome = rollup([reading("D-7741", km = 41283, at = "02:14:09"),
reading("D-7741", km = 41283, at = "02:14:09")])
assert outcome.distanceKm == ???
# after the owner decides: an exact duplicate is dropped, and counted
test "an exact duplicate reading is dropped and reported":
outcome = rollup([reading("D-7741", km = 41283, at = "02:14:09"),
reading("D-7741", km = 41283, at = "02:14:09")])
assert outcome.distanceKm == 0
assert outcome.droppedRecords == 1go deeper
Recognise that being unable to finish the assertion usually means nobody has decided what correct is for that input. Ask rather than guess, and never let a value you invented become the rule.
Explain the two shapes: a requirement that never covered the input, and a property with no observable to assert against. Show how you choose an observable for the second so an assertion becomes possible.
Demonstrate the routing: name the case precisely, bring two or three candidate answers with consequences, encode the decision in the assertion and the test name, and keep an undecided case visibly pending instead of guessing.
Frame this as the cheapest requirements-review loop the team has, and be ready to say how you keep it flowing — who is on the hook for decisions, and how you stop the pending-case list from quietly becoming a backlog nobody reads.
## What a blocked assertion actually is In test-first work you build a concrete case — a specific input, a specific state — and then have to write down what correct looks like for it. The thing that says whether observed behaviour is right is the **oracle**. When you cannot finish the assertion line, you have not hit a testing problem; you have discovered that the oracle for this case does not exist yet. Somebody was going to have to decide it. The only question is whether they decide it consciously now, or whether an implementer decides it by accident and the decision is discovered in production. ## The two flavours, and they need different responses **The requirement is silent.** The specification simply never addressed this input. Consider a fleet telematics ingest whose nightly rollup summarises the previous day per device. The written requirement covers readings arriving in order. You write the case where a device reports the same position twice with identical timestamps — and stop, because "total distance" for a duplicate is undefined. Should the duplicate be counted, dropped, or should the whole device-day be quarantined? Nothing in the requirement decides it, and each answer is defensible. **The requirement is stated in terms nothing observable corresponds to.** The stated behaviour is real but no assertable outcome has been named. "The nightly run must not exhaust resources" is the classic shape: a rollup budgeted at 6 h 12 m has previously fallen over holding 1,438 device buffers open at once, so the ask is genuine — but *exhaust* is not a value anything returns. Here the work is to choose the observable that stands for it: that the ingest holds at most a bounded number of open buffers, that a bounded queue of 4,096 readings refuses the 4,097th with a defined outcome rather than growing, that the run reports a high-water mark you can assert against a ceiling. Once an observable exists, the assertion writes itself. ## What to do, in order 1. **Stop, and name the case precisely.** "Two readings with identical device id and identical capture timestamp" is actionable. "Duplicates are unclear" is not. 2. **Bring candidate answers, not an open question.** Two or three options with their consequences convert a vague conversation with a product owner or domain expert into a decision that takes minutes. 3. **Encode the decision, visibly.** The assertion carries it, and the test name states it in words, so the next reader learns the rule without opening the requirement. 4. **If the decision cannot be had now, keep the gap visible.** Move to cases that *are* decided and leave the undecided one explicitly pending with a note naming who owes the answer. A pending case is a tracked question; a guessed assertion is an untracked one. 5. **Never resolve it by writing the code first.** Implementing and then asserting whatever comes out is the failure mode this whole signal exists to prevent — the code silently becomes the specification, and the decision is never reviewed by anyone who could have made it differently. ## Why this is a senior question Juniors usually experience the blocked assertion as being stuck and reach for the fastest unblock: guess a value, or implement and then read the answer off the result. Both destroy the signal. The senior move is recognising the block as a finding, and being socially willing to interrupt a coding task to get a decision. Doing that well is mostly about arriving with candidates: the difference between "what should duplicates do?" and "we can count them, drop them, or quarantine the device-day — dropping is cheapest and loses a real distance if a device legitimately re-sends; which do you want?" is the difference between a week of ambiguity and a two-minute answer. ## Related judgement calls Sometimes the honest answer is that the case cannot happen. Then say so explicitly rather than leaving it undecided: assert the refusal, or record the impossibility where the next person will read it. And sometimes the block reveals that the case is not one behaviour but two — a duplicate from a device retrying is not the same event as a duplicate from a replayed batch, and once separated each has an obvious expected result. Splitting a case whose oracle is ambiguous is often the quickest route to an assertion you can actually write. ## The distilled point An assertion you cannot write is not friction; it is the requirements defect the practice is best at finding, delivered at the cheapest moment. The strong answer treats it as information, routes it to whoever owns the behaviour, and makes sure the resolution ends up in the suite rather than in someone's memory.
- The requirement is 'the nightly run must not exhaust resources'. How do you get from that to an assertion?Choose an observable that stands for the property. Give the component a bounded buffer and assert that the item past the bound is refused with a defined outcome rather than accepted; or have the run report a high-water mark and assert it stays under a stated ceiling. The value of doing this before the code is that the ceiling and the refusal behaviour become explicit decisions rather than emergent ones.
- The person who owns the decision is unavailable for two days. What do you do?Keep the undecided case explicitly pending with a note naming the question and who owes the answer, and carry on with the cases that are decided. That keeps the gap tracked. The one thing to avoid is inventing an expected value to unblock yourself, because a guessed assertion looks identical to a decided one once it is green.
- When is splitting the case the right response to an ambiguous expected result?When the input you chose actually covers two different situations with different correct answers — a device re-sending versus a batch being replayed, for example. The ambiguity was in the case, not the requirement. Splitting it usually makes each expected result obvious, and it leaves two clearly named tests instead of one hedged assertion.
saying these in an interview costs you the question
- Guesses an expected value to get the test compiling
- Writes the code first and asserts whatever it returns
- Calls the block a testing problem rather than a requirements gap
- Takes an open question to the owner with no candidate answers
- Deletes the awkward case instead of leaving it visibly pending