Practical guide
Specify what rejection must leave unchanged
A negative test is useful when it checks how the system handles an unacceptable input or failed operation. “It returned an error” is often only part of that requirement.
Define failure behavior before writing the test
A rejected transfer might need to preserve both balances, avoid sending a notification and leave no successful transaction record. A parser error might need a stable location and no partial update. Identify the observation that would distinguish a safe rejection from a superficially failing operation that already caused harm.
Illustrative example: transfer rejection
| Case | Expected response | State to inspect |
|---|---|---|
| Amount exceeds balance | Insufficient-funds rejection | Both balances unchanged |
| Destination is missing | Unknown-destination rejection | No debit or transfer record |
| Same request repeats | Previously recorded outcome | No second debit |
The repeated request is not necessarily invalid: the contract might require idempotent success. That distinction belongs in the requirement. Do not classify every unusual input as an expected error and accidentally hide supported behavior.
Arrange the real path to the failure
Supply inputs through the supported entry point and establish the required starting state. If validation normally blocks an input earlier, determine which layer owns the rejection. A unit test that calls an unreachable internal state can help harden a function but does not alone reproduce a public defect. For authorization cases, establish whether the caller can actually control the relevant value.
Make the assertion sensitive to the defect
Illustrative test outline:
snapshot balances and transaction count
submit a transfer exceeding the available balance
assert the specified rejection
assert balances and transaction count equal the snapshot
Use an independent expected result rather than copying the implementation's calculation into the test. Confirm the assertion fails when the forbidden effect occurs. A test name or requirement annotation is not evidence that this observation was made.
Retain evidence for future changes
Connect the test to its requirement, record the executed revision and keep any minimized reproducer with the resulting defect record. In Proof, scenario or witness annotations describe intended coverage; observed execution evidence is separate. A missing error case may require a requirement amendment as well as a test. Rerun the affected behavior after the fix, and preserve unresolved cases explicitly.
Follow the evidence
Add one assertion about state that must remain unchanged after rejection.
Review one rejection path. Bring the requirement and negative test so we can assess whether it checks both the expected response and the state that must remain unchanged.