Skip to content
AITroveRead. Build. Understand.
Make this comfortable

Spring transactional event test: cross an actual commit boundary

Last updated: 5 Oct 20264 min read
tutorial
IntermediateBy AITrove Editorial

An AFTER_COMMIT listener cannot be proved by a test transaction that always rolls back.

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

Java
@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.

spring
spring-boot
testing
transaction-event-commit-test
Storage details