unit-test-application-events

Unit Testing Application Events

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "unit-test-application-events" with this command: npx skills add giuseppe-trisciuoglio/developer-kit/giuseppe-trisciuoglio-developer-kit-unit-test-application-events

Unit Testing Application Events

Overview

Provides actionable patterns for testing Spring ApplicationEvent publishers and @EventListener consumers using JUnit 5 and Mockito — without booting the full Spring context.

When to Use

  • Writing unit tests for event publishers or listeners

  • Verifying that an event was published with correct payload

  • Testing @EventListener method invocation and side effects

  • Testing event propagation through multiple listeners

  • Validating async event handling (@Async

  • @EventListener )
  • Mocking ApplicationEventPublisher in service tests

Instructions

  • Add test dependencies: spring-boot-starter , JUnit 5, Mockito, AssertJ

  • Mock ApplicationEventPublisher: use @Mock on the publisher field in the service under test

  • Capture events with ArgumentCaptor: ArgumentCaptor.forClass(EventType.class) to inspect published payload

  • Verify listener side effects: invoke listener directly against mocked dependencies

  • Test async handlers: use Thread.sleep() or Awaitility — then assert the async operation was called

  • Add validation checkpoints:

  • After capturing an event, confirm eventCaptor.getValue() is not null before asserting fields

  • If the listener is not invoked, verify publishEvent() was called with the correct event type

  • If async assertions fail, increase wait time and check the executor pool is not saturated

  • Cover error scenarios: assert listeners handle exceptions gracefully

Examples

Maven

<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

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 Event and Publisher Test

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 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 savedUser = userRepository.save(new User(name, email)); eventPublisher.publishEvent(new UserCreatedEvent(this, savedUser)); return savedUser; } }

Unit Test for Event Publishing

@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&#x3C;UserCreatedEvent> eventCaptor = ArgumentCaptor.forClass(UserCreatedEvent.class);

userService.createUser("Alice", "alice@example.com");

verify(eventPublisher).publishEvent(eventCaptor.capture());
assertThat(eventCaptor.getValue().getUser()).isEqualTo(newUser);

} }

Listener Direct Test

@Component public class UserEventListener { private final EmailService emailService;

public UserEventListener(EmailService emailService) { this.emailService = emailService; }

@EventListener public void onUserCreated(UserCreatedEvent event) { emailService.sendWelcomeEmail(event.getUser().getEmail()); } }

class UserEventListenerTest {

@Test void shouldSendWelcomeEmailOnUserCreated() { EmailService emailService = mock(EmailService.class); UserEventListener listener = new UserEventListener(emailService);

User user = new User(1L, "Alice", "alice@example.com");
listener.onUserCreated(new UserCreatedEvent(this, user));

verify(emailService).sendWelcomeEmail("alice@example.com");

}

@Test void shouldNotThrowWhenEmailServiceFails() { EmailService emailService = mock(EmailService.class); doThrow(new RuntimeException("down")).when(emailService).sendWelcomeEmail(any());

UserEventListener listener = new UserEventListener(emailService);
User user = new User(1L, "Alice", "alice@example.com");

assertThatCode(() -> listener.onUserCreated(new UserCreatedEvent(this, user)))
  .doesNotThrowAnyException();

} }

Async Listener Test

@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");
listener.onUserCreatedAsync(new UserCreatedEvent(this, user));

Thread.sleep(200); // checkpoint: allow async executor to run
verify(slowService).processUser(user);

} }

Best Practices

  • Mock ApplicationEventPublisher — never let it post to a real context in unit tests

  • Capture events with ArgumentCaptor and assert field-level equality, not just type

  • Test listeners in isolation: construct them with mocked dependencies and call the handler method directly

  • Cover error paths: listeners must not propagate exceptions to publishers

  • Async listeners: prefer Awaitility over Thread.sleep() for deterministic waits

  • Keep events immutable and serializable — test both if events cross JVM boundaries

Constraints and Warnings

  • Do not test Spring's own event infrastructure — focus on your business logic and event payload

  • @Async requires @EnableAsync — tests using Thread.sleep may still pass even if the async proxy is not wired in the test; use a mock verify instead

  • Spring does not guarantee listener order — do not write tests that depend on execution sequence unless you add @Order

  • Avoid Thread.sleep() in CI environments — it makes tests flaky under load; replace with Awaitility .atMost() blocks

  • Events crossing JVM boundaries need serialization tests — null fields in remote listeners often mean missing Serializable

References

  • Spring ApplicationEvent Javadoc

  • ApplicationEventPublisher Javadoc

  • @EventListener Javadoc

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Coding

shadcn-ui

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

tailwind-css-patterns

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

unit-test-bean-validation

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

react-patterns

No summary provided by upstream source.

Repository SourceNeeds Review