Suppress the Document, Prove the Discount Survived
A story suppressed a contract document and an order confirmation for add-on discounts. The hard part was proving the suppression did not take the billing discount down with it.
The story asked for two things not to happen. No contract document. No order confirmation.
The dangerous part of that story is everything it did not mention.
Two Outputs, One Trigger, Two Answers
The feature came out of an earlier defect: a discount applied to an add-on had wrongly generated a new contract and an order confirmation, as if the customer had signed for something.
So the rule was rewritten around the class of product the discount attaches to. A discount on the primary broadband bundle is contractual — it changes what the customer pays for the thing they signed up for, and both outputs are generated. The identical discount mechanism applied to a child product on the same subscription is not contractual, and both outputs must be suppressed.
From the tester's chair the two cases are indistinguishable in every visible respect. Same screen. Same discount fields. Same modify order submitted at the end. One attribute of the target product decides whether two contractual outputs exist.
That alone makes it a good test. It is not what made it a hard one.
The Assertion That Pulls the Other Way
The suppressed document is not a standalone artefact. The discount rides along with it — down into the billing system, onto the subscription, onto the invoice.
So the same code path that stops the paperwork has a very natural way to fail: it stops too much. Suppress the comms, and the underlying discount quietly stops syncing. Nothing errors. The customer gets no misleading contract, which is what the story asked for, and also does not get the discount, which nobody asked for and nobody sees until an invoice lands.
That gives one test case two assertions pointing in opposite directions:
- Prove the absence. No contract document generated, no order confirmation sent. This is what the story is about, so it is what gets written first and what a reviewer checks for.
- Prove the survival. The discount is still applied, the net price is still right, and it still reaches the billing system and the invoice. Nothing in the story text tells you to assert this, which is exactly why it has to be written down.
The second assertion is the whole value of the case. Without it, the test proves the feature can delete things.
The Step That Was Not a Step
Execution added the part I would not have predicted from reading the story.
The test cases all contained a step to confirm terms and conditions in a popup. It appears in every discount change anyone had ever run, and it had been treated as an independent action for as long as those cases had existed.
It is not independent. The terms and conditions window is a downstream effect of document generation. No document, no window.
So on the branch where a discount is applied to an add-on alone — the branch that generates nothing — the modal never appears at all. The step cannot be executed. The tester following the case is stuck at a step that was never going to happen, and the natural reaction is to log a failure.
We corrected the cases live: that step is now conditional on the generate branch.
What This Was Not
It was not a defect. The missing popup was correct behaviour, precisely as designed, on a branch built to behave that way.
It was not a gap in the story either. The story described the outputs it wanted suppressed, accurately.
It was a test-design error, and a specific one: a step inherited from the happy path, carried into a negative branch, where it silently changes meaning. Left uncorrected it produces the worst kind of bug report — one that is wrong, takes a developer an afternoon to disprove, and costs the tester credibility on the next report that is right.
Suppression Has a Blast Radius
Two habits came out of this, and both are cheap.
When a story suppresses an output, write the paired assertion for everything that output was travelling with. Suppression bugs do not hide in what gets suppressed on purpose. They hide in what gets suppressed by accident, one layer down, where the assertion nobody wrote would have caught it.
Then walk your own steps. Every confirmation modal, terms gate, notification and status flip in your existing cases is a candidate for the same coupling. Ask which upstream output it actually depends on, and mark it conditional on the same branch — because a step that "always appears" in the generate path vanishes in the suppress path, and turns a perfectly valid negative case into a false failure.
Which step in your test cases has been there so long that nobody remembers what produces it?