An AFTER_COMMIT listener cannot be proved by a test transaction that always rolls back.
Spring transactional event test: cross an actual commit boundary
The phase is the behavior
A service publishes ReceiptAccepted inside a transaction and an @TransactionalEventListener with AFTER_COMMIT starts follow-up work. A test method annotated @Transactional rolls back by default, so the listener should not run in that test. Verifying only that publishEvent was called does not prove after-commit delivery. Transaction events] separate event publication from phase execution.
Commit in a controlled test
Use TestTransaction to mark the active test transaction for commit and end it, then assert a synchronous listener's observable result. Or run the service without a test-managed transaction and verify committed state in a fresh transaction. Do not send irreversible external traffic from a unit test; substitute the final gateway while keeping the service, transaction manager and listener real. For durable cross-process delivery, an outbox] remains the stronger contract.
Cover rollback too
Invoke the service on a path that throws after publication, and assert the AFTER_COMMIT listener did not perform its action. If the listener is asynchronous, wait on a bounded observable completion signal rather than sleeping a guessed duration. Test both commit and rollback with the same event type so a misplaced plain @EventListener cannot quietly pass.
Implementation contract
@SpringBootTest
@Transactional
class ReceiptCommittedEventTest {
@Autowired ReceiptWriteService receipts;
@Autowired ReceiptNotificationProbe probe;
@Test void notifiesOnlyAfterCommit() {
receipts.accept(new ReceiptCommand("R-47", 4700));
assertThat(probe.count()).isZero();
TestTransaction.flagForCommit();
TestTransaction.end();
assertThat(probe.count()).isEqualTo(1);
}
}Cost and verification
A commit test writes durable rows and needs cleanup or isolated data. It is slower than a publisher mock but exercises the phase that matters. An asynchronous listener adds a scheduling boundary and needs bounded synchronization.
Common Mistakes
- Do not expect AFTER_COMMIT to fire after a test transaction rolls back.
- Do not equate publishEvent invocation with listener completion.
- Do not use an unbounded sleep to wait for asynchronous delivery.
Read next
Spring transaction events: run a listener after commit without claiming durability, Spring outbox transaction boundaries: where atomicity ends, Spring Boot HTTP test: client rollback does not own server writes, Spring command rollback test: inspect state after an injected failure, Spring relay failure trace: inspect persisted state at each cut point.
