Worker recovery tests should assert the state left behind at each failure point, not just that an exception occurred.
Test a Spring worker at admission, lease and replay boundaries
Three local observations
The bounded executor test fills one running and one waiting slot, then sees a rejected third task. The outbox test checks claim refusal before expiry, takeover at expiry and a zero-row stale acknowledgement. The consumer test verifies that a repeated event ID does not alter the stock again. Each assertion examines the state the next worker would encounter.
These are separate fixtures, not an end-to-end worker. They do not share one HTTP handler, broker or database. The layer matrix explains why a small local check cannot prove a deployment property. A useful integration test would stop a relay after commit, restart it, deliver the same event twice and inspect destination state and acknowledgement history.
Inject failure at every handoff
A larger suite should fail between receipt commit and claim, after claim but before send, after send but before acknowledgement, and after destination commit but before broker ack. Use a target database and actual broker test environment before making guarantees about lock behavior or redelivery. Record the maximum delay and retry count expected by the product. Without those values, a green unit suite can still conceal a user-visible stuck receipt.
Checked source
assertThrows(TaskRejectedException.class,
() -> executor.submit(overflowReceiptDispatch));
assertEquals(0, acknowledge(jdbc, "E-41", "stale-token"));
assertThrows(DuplicateKeyException.class,
() -> apply(jdbc, transaction, "E-41", 4));Verification boundary
BoundedExecutorContractTest.thirdTaskIsRejectedWhileOneRunsAndOneWaits, OutboxLeaseContractTest.expiredLeaseCanBeReclaimedButOldWorkerCannotAcknowledge and ConsumerDedupContractTest.repeatedEventIdCannotApplyTheStockMutationTwice runs in the downloadable Spring source kit. The excerpt is shortened; the kit contains the complete tests.
Costs and limits
The three assertions come from independent local tests and are condensed here. The kit has no crash injector, broker or target database. Those tests are needed to measure recovery time and verify behavior across process boundaries.
Common Mistakes
- Do not mistake three green unit fixtures for one delivery service.
- Do not assert only status codes when the failure is about retained state.
- Do not omit a test for the send-before-acknowledgement gap.
Read next
Spring task executors: reject work when every slot is occupied, Spring outbox claims: allow recovery after a worker lease expires, Spring consumer deduplication: commit the event ID with the mutation.
