tessl install github:giuseppe-trisciuoglio/developer-kit --skill unit-test-application-eventsgithub.com/giuseppe-trisciuoglio/developer-kit
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.
Review Score
80%
Validation Score
11/16
Implementation Score
65%
Activation Score
100%
Test Spring ApplicationEvent publishers and event listeners using JUnit 5. Verify event publishing, listener execution, and event propagation without full context startup.
Use this skill when:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<scope>test</scope>
</dependency>dependencies {
implementation("org.springframework.boot:spring-boot-starter")
testImplementation("org.junit.jupiter:junit-jupiter")
testImplementation("org.mockito:mockito-core")
testImplementation("org.assertj:assertj-core")
}// 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<UserCreatedEvent> 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);
}
}// 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();
}
}class UserCreatedEvent extends ApplicationEvent {
private final User user;
private final List<String> notifications = new ArrayList<>();
public UserCreatedEvent(Object source, User user) {
super(source);
this.user = user;
}
public void addNotification(String notification) {
notifications.add(notification);
}
public List<String> 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());
}
}@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
}
}@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);
}
}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.