unit-test-application-events

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

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.
使用JUnit 5测试Spring ApplicationEvent发布者和事件监听器。无需启动完整上下文即可验证事件发布、监听器执行和事件传播。

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
在以下场景中使用本技能:
  • 测试ApplicationEventPublisher的事件发布功能
  • 测试@EventListener方法的调用
  • 验证事件监听器的逻辑和副作用
  • 测试事件通过监听器的传播过程
  • 想要快速进行事件驱动架构测试
  • 测试同步和异步事件处理

Setup: Event Testing

设置:事件测试

Maven

Maven

xml
<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>
xml
<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>

Gradle

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")
}
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<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);
  }
}
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<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);
  }
}

Testing Event Listeners

测试事件监听器

@EventListener Annotation

@EventListener注解

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();
  }
}
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<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());
  }
}
java
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());
  }
}

Testing Conditional Event Listeners

测试条件事件监听器

@EventListener with Condition

带条件的@EventListener

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
  }
}
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

结合@Async与@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);
  }
}
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
  • 在单元测试中Mock ApplicationEventPublisher
  • 使用ArgumentCaptor捕获已发布的事件
  • 显式测试监听器的副作用
  • 测试监听器中的错误处理
  • 保持事件监听器专注于单一职责
  • 捕获事件时验证事件数据的完整性
  • 测试同步和异步事件处理

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
  • 不Mock发布者就测试实际事件发布
  • 未验证监听器的调用
  • 未捕获事件详情
  • 测试监听器注册而非逻辑
  • 未处理监听器异常

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.
事件未被捕获:验证ArgumentCaptor的类型是否与事件类匹配。
监听器未被调用:确保事件已实际发布且监听器已注册。
异步监听器时序问题:使用Thread.sleep()或Awaitility等待异步操作完成。

References

参考资料