Search code examples
spring-data-jpadomain-events

@TransactionalEventListener annotated method not invoked in @Transactional test


I'm trying to implement domain event publishing from an entity by following the examples mentioned on the post below:

Example for @DomainEvents and @AfterDomainEventsPublication

However I haven't managed to have Spring calling my method annotated with @TransactionalEventListener.

See below the entity, service, event listener and test code:

@Entity
public class Book extends AbstractAggregateRoot<Book>
{
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;

    @Column(unique = true)
    private String isbn;

    @Column
    private String name;

    public Book(String isbn, String name)
    {
        this.isbn = isbn;
        this.name = name;
    }

    public void purchase()
    {
        registerEvent(new BookPurchasedEvent(id));
    }

    // getters omitted for brevity
}

Service:

@Service
@Transactional
public class BookService
{
    private final BookRepository bookRepository;

    public BookService(BookRepository bookRepository)
    {
        this.bookRepository = bookRepository;
    }

    public void purchaseBook(Integer bookId)
    {
        Book book = bookRepository.findById(bookId)
                                .orElseThrow(NoSuchElementException::new);

        book.purchase();

        bookRepository.save(book);
    }
}

Listener:

@Service
public class EventListener
{
    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    @TransactionalEventListener
    public void handleEvent(BookPurchasedEvent event)
    {
        logger.info("Received event {}", event);
    }
}

Test:

@RunWith(SpringRunner.class)
@SpringBootTest
@Transactional
public class BookEventsTest
{
    @Autowired
    private BookService bookService;

    @Autowired
    private EntityManager entityManager;

    @Test
    public void test()
    {
        Book book = new Book("abcd-efgh", "El Quijote");
        book = entityManager.merge(book);

        bookService.purchaseBook(book.getId());
    }
}

The log message from the listener is not logged. It works though when deployed as a REST service and invoked e.g. via Postman


Solution

  • Got it. Since my test is annotated with @Transactional, the transaction wrapping the test method will be rolled back. Therefore the method annotated with @TransactionalEventListener won't be called, since by default it triggers at the phase TransactionPhase.AFTER_COMMIT (and I'm not interested in having it called unless the transaction is successful). So the working version of the test looks as follows:

    @RunWith(SpringRunner.class)
    @SpringBootTest
    public class BookEventsTest
    {
        @Autowired
        private BookService bookService;
    
        @Autowired
        private BookRepository bookRepository;
    
        @MockBean
        private EventListener eventListener;
    
        private Book book;
    
        @Before
        public void init() {
            book = bookRepository.save(new Book("abcd-efgh", "El Quijote"));
        }
    
        @After
        public void clean() {
            bookRepository.deleteAll();
        }
    
        @Test
        public void testService()
        {
            bookService.purchaseBook(book.getId());
    
            then(eventListener)
                    .should()
                    .handleEvent(any(BookPurchasedEvent.class));
        }
    }