Skip to main content
skill CX Codex

unit-test-application-events — Claude Code skill for Spring event

Unit test patterns for Spring events: verify publishing through ApplicationEventPublisher and exercise @EventListener handlers without booting the context.

Install into Claude Code · Skills CLI (Vercel) All methods and platforms ↓
npx skills add giuseppe-trisciuoglio/developer-kit --skill unit-test-application-events -a claude-code
Open on GitHub

What unit-test-application-events does

The skill hands you JUnit 5 and Mockito patterns for Spring ApplicationEvent publishers and listeners. No Spring context gets started. The publisher is mocked, the event is grabbed with ArgumentCaptor, and payload fields are asserted one by one. Listeners are tested in isolation: build them with mocked dependencies and call the handler method directly.

There is a separate section on async handlers, @Async plus @EventListener. Watch the contradiction here — the sample test waits with Thread.sleep(), while the same file's best practices recommend Awaitility for deterministic waits. When an async listener test fails, check whether the executor pool is saturated before you bump the wait time.

Dependencies are spring-boot-starter, JUnit 5, Mockito and AssertJ, with copy-ready blocks for Maven and Gradle. The skill states plainly that you should not test Spring's own event infrastructure — test your business logic and the event payload. Also note that @Async needs @EnableAsync.

What it helps with

  • assert a service published an event with the right payload
  • test an @EventListener without starting a Spring context
  • mock ApplicationEventPublisher inside a service test
  • capture the event with ArgumentCaptor and compare its fields
  • confirm a failing dependency does not blow up the listener

Example prompts for the agent

  • write a unit test for UserCreatedEvent publishing in UserService
  • verify with ArgumentCaptor that the event carried the right user
  • test an async @EventListener with @Async

How to install unit-test-application-events

1Skills CLI (Vercel)

npx skills add giuseppe-trisciuoglio/developer-kit --skill unit-test-application-events -a claude-code

2Manually: put the files in place

~/.claude/skills/unit-test-application-events/SKILL.md

Copy the whole skill folder; for one project use .claude/skills/unit-test-application-events/

Step by step, with commands for each platform: How to install skills in Claude Code: five ways

Questions about unit-test-application-events

How do I install unit-test-application-events in Claude?

Run npx skills add giuseppe-trisciuoglio/developer-kit --skill unit-test-application-events -a claude-code. The skill file then lands at ~/.claude/skills/unit-test-application-events/SKILL.md.

What does unit-test-application-events do?

It provides unit test patterns for Spring event publishers and listeners: mocking ApplicationEventPublisher, capturing events with ArgumentCaptor, calling @EventListener methods directly, and checking async handling.

Which dependencies do the tests need?

spring-boot-starter, JUnit 5, Mockito and AssertJ. The skill includes Maven and Gradle blocks, and mentions @EnableAsync for async listeners.

Source text

SKILL.md · MIT
---
name: unit-test-application-events
description: Provides patterns for unit testing Spring application events. Validates event publishing with ApplicationEventPublisher, tests @EventListener annotation behavior, and verifies async event handling. Use when writing tests for event listeners, mocking application events, or verifying events were published in your Spring Boot services.
allowed-tools: Read, Write, Bash, Glob, Grep
---

# 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

1. **Add test dependencies**: `spring-boot-starter`, JUnit 5, Mockito, AssertJ
2. **Mock ApplicationEventPublisher**: use `@Mock` on the publisher field in the service under test
3. **Capture events with ArgumentCaptor**: `ArgumentCaptor.forClass(EventType.class)` to inspect published payload
4. **Verify listener side effects**: invoke listener directly against mocked dependencies
5. **Test async handlers**: use `Thread.sleep()` or Awaitility — then assert the async operation was called
6. **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
7. **Cover error scenarios**: assert listeners handle exceptions gracefully

## Examples

### 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>
```

### 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")
}
```

### Custom Event and Publisher Test

```java
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

```java
@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", "[email protected]");
    when(userRepository.save(any(User.class))).thenReturn(newUser);

    ArgumentCaptor<UserCreatedEvent> eventCaptor = ArgumentCaptor.forClass(UserCreatedEvent.class);

    userService.createUser("Alice", "[email protected]");

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

### Listener Direct Test

```java
@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", "[email protected]");
    listener.onUserCreated(new UserCreatedEvent(this, user));

    verify(emailService).sendWelcomeEmail("[email protected]");
  }

  @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", "[email protected]");

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

### Async Listener Test

```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", "[email protected]");
    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](https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/ApplicationEvent.html)
- [ApplicationEventPublisher Javadoc](https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/ApplicationEventPublisher.html)
- [`@EventListener` Javadoc](https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/event/EventListener.html)

© giuseppe-trisciuoglio · MIT · giuseppe-trisciuoglio/developer-kit

Similar skills

Everything for Testing and QA →

From the same set93

langchain4j-testing-strategies Guides Java tests for LangChain4j, from mocked ChatModel responses to RAG workflows and Testcontainers-based integration checks. CX Codex +1 3,700installs aws-sdk-java-v2-s3 Hands your agent ready-made Amazon S3 patterns for AWS SDK for Java 2.x: buckets, object uploads, multipart, presigned URLs. CX Codex +1 3,700installs langchain4j-mcp-server-patterns Provides design patterns for building Java MCP servers and connecting them to LangChain4j. Useful when exposing tools in Spring Boot and securing agent execution. CX Codex +1 3,700installs langchain4j-vector-stores-configuration A skill that shows how to wire a LangChain4J vector store for RAG: connections, embedding dimensions, metadata filters. CX Codex +1 3,700installs unit-test-config-properties Provides JUnit 5 patterns to unit test Spring Boot @ConfigurationProperties using ApplicationContextRunner without starting a full application context. CX Codex +1 3,700installs unit-test-caching Unit test patterns for Spring Cache annotations: cache hits and misses, eviction, SpEL keys, all without booting a full Spring context. CX Codex +1 3,700installs langchain4j-ai-services-patterns A skill that walks your agent through declarative AI Services in LangChain4j: interfaces, annotations, memory and tool calling. CX Codex +1 3,700installs qdrant Provides integration patterns for the Qdrant vector database in Java and Spring Boot with LangChain4j. Helps build semantic search and vector stores for RAG. CX Codex +1 3,700installs

Project sponsors

Companies that keep this analytics open
Gold sponsor

Impressions, clicks and code copies — open statistics. We keep the numbers public for the same reason NeBlask itself is public. Become a sponsor