Why should each invalid equivalence class get its own test case rather than one combined case?
answer
- The first failing check ends the request
- One class hides behind another
- Everything else in the case stays valid
- Assert which rule refused, not that it refused
- Valid classes may share a case
basics
~20 sBecause a rejected input usually stops at the first failing check, so a second invalid value in the same case is never reached. Isolating one invalid class per case keeps every rejection path exercised and every failure diagnostic.
solid answer
~50 sValidation is normally fail-fast: the first rule that trips produces the refusal and the rest never run. Put two invalid values in one request and the case still passes, but only one rejection rule was tested — the second class is **masked**, and you have a green test that proves nothing about it. So the rule of thumb is one invalid class per case, with every other field set to an ordinary valid representative, and the assertion pinned to *which* rule fired rather than to the fact that something was refused. Valid classes are different: a request must satisfy every rule to be accepted, so one case can legitimately land in several valid classes at once. If the system reports all violations at once instead of stopping at the first, the masking risk drops — but you still assert per class.
code
pseudocode · 11 lines# MASKED: two invalid classes in one request
book(leadDays = -4, clinicCode = "NOT-A-CLINIC")
assert refused # passes even if the clinic rule does not exist
# ISOLATED: one invalid class per case, all other fields valid
book(leadDays = -4, clinicCode = "CARD-02")
assert reason == "TOO_SOON"
book(leadDays = 37, clinicCode = "NOT-A-CLINIC")
assert reason == "UNKNOWN_CLINIC"
assert appointmentsCreated == 0go deeper
Know the rule and the reason in one line: a refused request stops at the first failing check, so two invalid values in one case means one of them was never tested.
Explain masking concretely, show that all other fields must hold ordinary valid values, and be able to say why the same restriction does not apply to valid classes.
Demonstrate assertion discipline — reason codes over generic refusal, plus checking that nothing was written — and explain how check ordering silently changes what a combined case covers during a refactor.
Own the standard: what a negative case must assert to be accepted in review, and how a team keeps its rejection suite from decaying into a pile of cases that all assert the same generic refusal.
## The masking problem Equivalence partitioning tells you to test one representative per class. The moment you have several fields, a practical question follows: how many classes may one test case cover? The answer is not symmetric between valid and invalid classes, and the asymmetry is the whole point of this question. Consider a booking request to a hospital appointment scheduler carrying a lead time in days, a clinic code and a referral identifier. Suppose you want to cover two invalid classes: lead time below the accepted minimum, and a clinic code that does not exist. It is tempting to send one request with both, see it refused, and tick off two classes with one case. That case will almost certainly pass. It will also almost certainly have tested only one of the two classes. Validation is usually **fail-fast**: the handler checks the lead time, finds it out of range, returns *requested too soon*, and never looks at the clinic code. The unknown-clinic rule could be missing, misspelled or wired to the wrong message and your suite would stay green. The second class was **masked** by the first. Worse, the masking is order-dependent: reorder the validation checks during a refactor and the test silently starts covering the other class instead, without anyone noticing that it changed meaning. ## The rule Design one test case per invalid class. In that case, the field under test carries a representative of the invalid class; **every other field carries an ordinary valid representative**. Nothing else in the request is allowed to be a reason for refusal, so if the request is refused, it is refused for the reason you are testing. Valid classes are the mirror image. To be accepted, an input must satisfy every rule at once, so a single accepted request necessarily lands in one valid class per field, and packing several valid classes into one case costs you nothing in diagnosis — the case is expected to pass, and if it fails you have a small number of fields to look at. This is why a partition-derived suite typically has relatively few positive cases and one negative case per invalid class. ## Assert which rule fired The rule about one invalid class per case only pays off if the assertion is specific. A case that asserts *the request was refused* is satisfied by any refusal, including a refusal produced by a rule you did not intend to exercise. Pin the assertion to the identity of the violated rule — the machine-readable reason code, the field the message points at, the error category — so that a check firing for the wrong reason is a failure rather than a pass. In the scheduler, *requested too soon* and *beyond the booking horizon* are two classes with two reason codes; a suite that asserts only refusal cannot tell you that the two were swapped. Assert the absence of effects too. A rejected booking must not create an appointment, hold a slot, or send a confirmation. A rejection test that checks only the response can pass while a partially applied write has already happened, and that is a defect the negative case is in a perfect position to catch. ## When the rule relaxes Some systems are specified to collect all violations and report them together rather than stopping at the first. There the masking risk is much smaller, because both rules genuinely run. Two cautions remain. First, that behaviour is a specification claim, and the case that proves it — one request violating two rules, asserting that both reasons come back — is a test in its own right, not a licence to fold every invalid class into one case. Second, collecting violations is often only partial: cheap structural checks may be batched while an expensive lookup, such as verifying the clinic code against a registry, still short-circuits. If you fold classes together on an assumption about ordering, the suite starts depending on an implementation detail that nobody promised to keep. ## What it looks like in practice Suppose the scheduler's specification yields six invalid classes across three fields. The disciplined suite is six negative cases plus a small number of positive ones, and each negative case names the rule it is about. The tempting shortcut is two or three combined cases, which reads as a smaller, tidier suite and gives you the coverage of maybe two rules. The count is worse the other way round: the combined suite is smaller **and** covers less, which is the rare case where the tidier artefact is strictly the weaker one. Keep the negative cases boring, one reason each, and the day a validation rule is accidentally deleted, exactly one test goes red and its name tells you which rule it was.
- If the system reports every violation at once instead of stopping at the first, does the rule still apply?It relaxes but does not vanish. Batched reporting means both rules actually run, so masking is less likely — but the batching itself is a specified behaviour that needs its own case, and batching is often partial, with cheap structural checks collected while an expensive lookup still short-circuits. Keeping one invalid class per case leaves the suite independent of check ordering, which is an implementation detail nobody promised to preserve.
- What should a negative case assert beyond the rejection itself?The identity of the rule that fired — a reason code or the field the message names — so a check firing for the wrong reason fails instead of passing. And the absence of effects: no appointment created, no slot held, no confirmation sent. A rejection that returns the right message while leaving a partial write behind is exactly the defect a negative case is positioned to catch, and it is invisible if you assert only the response.
- Why is it acceptable for one case to cover several valid classes at once?Because acceptance requires satisfying every rule simultaneously, so any accepted request already sits in one valid class per field — there is no way to test a valid class in isolation. The diagnostic cost is low as well: the case is expected to pass, and if it fails the field list is short. That asymmetry is why partition-derived suites usually carry few positive cases and one negative case per invalid class.
saying these in an interview costs you the question
- Packs several invalid values into one case to save time
- Asserts only that the request was refused
- Assumes every validation rule always runs
- Applies the isolation rule to valid classes as well
- Leaves other fields invalid while testing one rule
- Never checks that a rejected request left no effect behind