"Waived" Does Not Mean Zero
A waived fee usually reaches the billing system as the full value plus an offsetting discount. A test asserting "fee = 0" passes against the broken build. How to test waivers properly.
The business rule says the charge is waived. The customer pays nothing. The invoice shows zero.
So the test asserts zero, it passes, and a real defect ships.
This one has caught me twice on the same programme, in two unrelated features, which is what turned it from an anecdote into a rule I now check for by default.
What "Waived" Actually Looks Like on the Wire
Take an early termination charge that is automatically waived for bereavement or goodwill cases.
The billing system does not receive a zero. It receives the real monetary value, plus a discount line that nets it to zero. Both lines exist. The customer-facing total is zero because two non-zero numbers cancel out.
Passing zero directly is wrong — and it is a known regression area on that programme, which is how I learned the shape in the first place.
Now the test. A junior tester — a reasonable one, reading a requirement that says "waived" — asserts that the charge is zero, or that no charge was sent.
That test passes against a genuinely broken build. The build that sends a bare zero produces exactly the same customer-facing invoice as the correct one. Same total. Same PDF. Different data, and the difference only matters later: in revenue reporting, in the audit trail, in the credit that has to be reversed if the termination is cancelled.
The Second Instance
Months later, a campaign bundle. The promotion's name contained the words inc. Activation Discount, and the activation rule is driven by that name rather than by a flag on the record.
The correct behaviour: charge the full £25 activation fee, and add an offsetting −£25 activation discount line. Net zero on the invoice.
The build omitted the fee entirely and sent activation as zero.
Identical shape. Identical trap. A test asserting "activation fee = 0" would have passed and shipped it.
Two independent features, two different teams, the same misunderstanding — and that is the moment a pattern becomes teachable. One instance is a war story. Two is a rule you can hand to someone on their first week.
The Rule
"Waived" is an outcome, not a payload.
When a charge is waived, assert two things:
- the real value is present on the charge line, and
- the offsetting discount is present and equal to it.
Never assert a bare zero. A zero total is the thing both the correct and the broken build have in common, which makes it the least informative assertion available to you.
Where Else This Shape Hides
Once you have the pattern, you start seeing it in features that are not called waivers at all. Anywhere a business rule says "free," "included," "no charge," or "at no cost," ask the same question: is that zero produced by an absence, or by two numbers cancelling?
- Free-for-N-months promotions. The discount must carry a term. If the term field is left indefinite, the customer gets a permanent free subscription and the cart looks completely correct — the defect lives only in the billing record's expiry.
- Included add-ons. A bundled item at £0 may be a real product with a real price and a 100% discount, or a genuinely zero-priced record. Those behave differently the moment someone modifies the subscription.
- Goodwill credits. Same question, and usually the same answer: full value plus offset.
In every case, the customer-facing view is identical and the stored data is not.
Why the Requirement Will Not Tell You
The requirement is written in business language, and in business language "waived" genuinely does mean the customer pays nothing. Nobody is being careless.
The gap is that the business rule and its technical implementation are two different sentences, and only one of them is written down in the story.
Which leads to the habit worth building: for any rule that produces a zero, ask how the zero is produced before you write the expected result. That question takes thirty seconds with a developer and it is the entire difference between a test that discriminates and a test that agrees with everything.
How many of your expected results assert a zero without asserting how that zero was built?