--- name: unit-test-application-events description: Testing Spring application events (ApplicationEvent) with @EventListener and ApplicationEventPublisher. Test event publishing, listening, and async event handling in Spring Boot applications. Use when validating event-driven workflows in your Spring Boot services. category: testing tags: [junit-5, application-events, event-driven, listeners, publishers] version: 1.0.1 --- # Unit Testing Application Events Test Spring ApplicationEvent publishers and event listeners using JUnit 5. Verify event publishing, listener execution, and event propagation without full context startup. ## When to Use This Skill Use this skill when: - Testing ApplicationEventPublisher event publishing - Testing @EventListener method invocation - Verifying event listener logic and side effects - Testing event propagation through listeners - Want fast event-driven architecture tests - Testing both synchronous and asynchronous event handling ## Setup: Event Testing ### Maven ```xml org.springframework.boot spring-boot-starter org.junit.jupiter junit-jupiter test org.mockito mockito-core test org.assertj assertj-core test ``` ### Gradle ```kotlin dependencies { implementation("org.springframework.boot:spring-boot-starter") testImplementation("org.junit.jupiter:junit-jupiter") testImplementation("org.mockito:mockito-core") testImplementation("org.assertj:assertj-core") } ``` ## Basic Pattern: Event Publishing and Listening ### Custom Event and Publisher ```java // Custom application event public class UserCreatedEvent extends ApplicationEvent { private final User user; public UserCreatedEvent(Object source, User user) { super(source); this.user = user; } public User getUser() { return user; } } // Service that publishes events @Service public class UserService { private final ApplicationEventPublisher eventPublisher; private final UserRepository userRepository; public UserService(ApplicationEventPublisher eventPublisher, UserRepository userRepository) { this.eventPublisher = eventPublisher; this.userRepository = userRepository; } public User createUser(String name, String email) { User user = new User(name, email); User savedUser = userRepository.save(user); eventPublisher.publishEvent(new UserCreatedEvent(this, savedUser)); return savedUser; } } // Unit test import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.ArgumentCaptor; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import static org.assertj.core.api.Assertions.*; import static org.mockito.Mockito.*; @ExtendWith(MockitoExtension.class) class UserServiceEventTest { @Mock private ApplicationEventPublisher eventPublisher; @Mock private UserRepository userRepository; @InjectMocks private UserService userService; @Test void shouldPublishUserCreatedEvent() { User newUser = new User(1L, "Alice", "alice@example.com"); when(userRepository.save(any(User.class))).thenReturn(newUser); ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(UserCreatedEvent.class); userService.createUser("Alice", "alice@example.com"); verify(eventPublisher).publishEvent(eventCaptor.capture()); UserCreatedEvent capturedEvent = eventCaptor.getValue(); assertThat(capturedEvent.getUser()).isEqualTo(newUser); } } ``` ## Testing Event Listeners ### @EventListener Annotation ```java // Event listener @Component public class UserEventListener { private final EmailService emailService; public UserEventListener(EmailService emailService) { this.emailService = emailService; } @EventListener public void onUserCreated(UserCreatedEvent event) { User user = event.getUser(); emailService.sendWelcomeEmail(user.getEmail()); } } // Unit test for listener class UserEventListenerTest { @Test void shouldSendWelcomeEmailWhenUserCreated() { EmailService emailService = mock(EmailService.class); UserEventListener listener = new UserEventListener(emailService); User newUser = new User(1L, "Alice", "alice@example.com"); UserCreatedEvent event = new UserCreatedEvent(this, newUser); listener.onUserCreated(event); verify(emailService).sendWelcomeEmail("alice@example.com"); } @Test void shouldNotThrowExceptionWhenEmailServiceFails() { EmailService emailService = mock(EmailService.class); doThrow(new RuntimeException("Email service down")) .when(emailService).sendWelcomeEmail(any()); UserEventListener listener = new UserEventListener(emailService); User newUser = new User(1L, "Alice", "alice@example.com"); UserCreatedEvent event = new UserCreatedEvent(this, newUser); // Should handle exception gracefully assertThatCode(() -> listener.onUserCreated(event)) .doesNotThrowAnyException(); } } ``` ## Testing Multiple Listeners ### Event Propagation ```java class UserCreatedEvent extends ApplicationEvent { private final User user; private final List notifications = new ArrayList<>(); public UserCreatedEvent(Object source, User user) { super(source); this.user = user; } public void addNotification(String notification) { notifications.add(notification); } public List getNotifications() { return notifications; } } class MultiListenerTest { @Test void shouldNotifyMultipleListenersSequentially() { EmailService emailService = mock(EmailService.class); NotificationService notificationService = mock(NotificationService.class); AnalyticsService analyticsService = mock(AnalyticsService.class); UserEventListener emailListener = new UserEventListener(emailService); UserEventListener notificationListener = new UserEventListener(notificationService); UserEventListener analyticsListener = new UserEventListener(analyticsService); User user = new User(1L, "Alice", "alice@example.com"); UserCreatedEvent event = new UserCreatedEvent(this, user); emailListener.onUserCreated(event); notificationListener.onUserCreated(event); analyticsListener.onUserCreated(event); verify(emailService).send(any()); verify(notificationService).notify(any()); verify(analyticsService).track(any()); } } ``` ## Testing Conditional Event Listeners ### @EventListener with Condition ```java @Component public class ConditionalEventListener { @EventListener(condition = "#event.user.age > 18") public void onAdultUserCreated(UserCreatedEvent event) { // Handle adult user } } class ConditionalListenerTest { @Test void shouldProcessEventWhenConditionMatches() { // Test logic for matching condition } @Test void shouldSkipEventWhenConditionDoesNotMatch() { // Test logic for non-matching condition } } ``` ## Testing Async Event Listeners ### @Async with @EventListener ```java @Component public class AsyncEventListener { private final SlowService slowService; @EventListener @Async public void onUserCreatedAsync(UserCreatedEvent event) { slowService.processUser(event.getUser()); } } class AsyncEventListenerTest { @Test void shouldProcessEventAsynchronously() throws Exception { SlowService slowService = mock(SlowService.class); AsyncEventListener listener = new AsyncEventListener(slowService); User user = new User(1L, "Alice", "alice@example.com"); UserCreatedEvent event = new UserCreatedEvent(this, user); listener.onUserCreatedAsync(event); // Event processed asynchronously Thread.sleep(100); // Wait for async completion verify(slowService).processUser(user); } } ``` ## Best Practices - **Mock ApplicationEventPublisher** in unit tests - **Capture published events** using ArgumentCaptor - **Test listener side effects** explicitly - **Test error handling** in listeners - **Keep event listeners focused** on single responsibility - **Verify event data integrity** when capturing - **Test both sync and async** event processing ## Common Pitfalls - Testing actual event publishing without mocking publisher - Not verifying listener invocation - Not capturing event details - Testing listener registration instead of logic - Not handling listener exceptions ## Troubleshooting **Event not being captured**: Verify ArgumentCaptor type matches event class. **Listener not invoked**: Ensure event is actually published and listener is registered. **Async listener timing issues**: Use Thread.sleep() or Awaitility to wait for completion. ## References - [Spring ApplicationEvent](https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/ApplicationEvent.html) - [Spring ApplicationEventPublisher](https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/ApplicationEventPublisher.html) - [@EventListener Documentation](https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/event/EventListener.html)