A rollback test must inspect each protected table after a failure inside the transaction.
Spring command rollback test: inspect state after an injected failure
Fail after the event insert
The local test invokes the managed command service with a test-only failure switch. The service updates the receipt, inserts an outbox row, then throws before saving its replay record. The assertion checks that the receipt still says new and both outbox and replay tables are empty. The exception alone would not prove rollback; the post-failure rows do.
The test uses a real TransactionTemplate and DataSourceTransactionManager against H2. It exercises the service proxy's method authorization too, after setting a test security context. The HTTP tests separately exercise bearer validation and filter rules. The JWT web test shows why direct service tests cannot replace filter-chain tests.
Add other failure positions later
Try duplicate event IDs, a database timeout during replay insertion and a failure after commit before the client receives its response. The last case is why replay matters. This test does not kill a process during commit or compare two independent database engines. Failure injection should be controlled in test configuration, never by a public request header.
Checked source
assertThrows(IllegalStateException.class, () -> managedService.change(
"tenant-east", "R-41", "rollback-41", command, true));
assertEquals("new", state("tenant-east"));
assertEquals(0, count("receipt_outbox"));
assertEquals(0, count("command_dedupe"));Verification boundary
TenantReceiptCommandFlowTest.failureAfterOutboxInsertRollsBackAllThreeTables in the downloadable Spring source kit. The excerpt is shortened; the kit contains the complete test.
Costs and limits
This is a synchronous local rollback test, not a power-loss, broker or target-database crash test. It cannot show whether an external side effect already occurred, because the fixture performs none. Use a real database and process-level fault test before claiming recovery behavior.
Common Mistakes
- Do not assert only that an exception was thrown.
- Do not make failure injection callable by an untrusted client.
- Do not confuse rollback before commit with recovery after commit.
Read next
Spring tenant command transaction: keep state, event and replay record together, Spring receipt outbox command: record intent without claiming delivery, Test Spring JWT authorization through filters, service proxy and SQL, Test a Spring worker at admission, lease and replay boundaries.
