plugins/developer-kit-java/skills/unit-test-application-events/SKILL.md
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.
npx skillsauth add giuseppe-trisciuoglio/developer-kit unit-test-application-eventsInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Provides actionable patterns for testing Spring ApplicationEvent publishers and @EventListener consumers using JUnit 5 and Mockito — without booting the full Spring context.
@EventListener method invocation and side effects@Async + @EventListener)ApplicationEventPublisher in service testsspring-boot-starter, JUnit 5, Mockito, AssertJ@Mock on the publisher field in the service under testArgumentCaptor.forClass(EventType.class) to inspect published payloadThread.sleep() or Awaitility — then assert the async operation was calledeventCaptor.getValue() is not null before asserting fieldspublishEvent() was called with the correct event type<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")
}
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;
}
}
@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);
}
}
@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();
}
}
@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);
}
}
ApplicationEventPublisher — never let it post to a real context in unit testsArgumentCaptor and assert field-level equality, not just typeThread.sleep() for deterministic waits@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@OrderThread.sleep() in CI environments — it makes tests flaky under load; replace with Awaitility .atMost() blocksSerializable@EventListener Javadocdevelopment
Explore codebase before committing to a change. Phase executor skill for specs.explore command.
development
Executes real end-to-end verification against a running application after specification implementation. Detects the application type, starts the local runtime (Docker, Node, Spring Boot, etc.), runs real tests (curl for REST APIs, Playwright for web SPAs, computer-use for desktop apps), verifies acceptance criteria from the functional specification, generates a markdown report, and tears down the environment. Use when: user asks to verify a completed spec with real tests, run e2e checks after implementation, validate acceptance criteria in a live environment, or test the feature for real after task completion.
development
Initialize Spec-Driven Development context — detects tech stack, conventions, architecture patterns, and bootstraps persistence backends. Triggers on 'sdd-init', 'init sdd', 'setup sdd', 'initialize sdd', 'setup project', 'initialize project context'. Creates/updates docs/specs/architecture.md & ontology.md (Constitution), and populates knowledge-graph.json.
development
Optimizes raw idea descriptions into structured prompts ready for the brainstorming workflow. TRIGGER when: user says "optimize for brainstorm", "prepare idea for brainstorm", "enhance this idea", "make this ready for brainstorming", "imposta per brainstorm", or wants to improve a feature idea before using /specs.brainstorm. DO NOT TRIGGER for code optimization, refactoring, or general prompt engineering tasks.