The infrastructure behind @RecordApplicationEvents currently uses a ThreadLocal to capture application events published by the current thread. Unfortunately, this rules out events published by asynchronous event listeners in turn. It would be nice if events would be captured that are published on threads spawned by the current execution thread.

Comment From: sbrannen

Related Issues

  • https://github.com/spring-projects-experimental/spring-modulith/issues/116

Comment From: simonbasle

The model falls apart for concurrent tests, and most notably for tests cases that instantiate the Test Class once. Typically, JUnit5 tests with @TestInstance(Lifecycle.PER_CLASS) (and TestNG as well if I understand correctly).

To be clear, turning the ApplicationEventsHolder ThreadLocal into an InheritableThreadLocal breaks the above case (as demonstrated by ParallelApplicationEventsIntegrationTests).

Unlike in Spring Modulith, we can't really assume that tests are not parallelized (or at least parallelized in a way that events collection won't get polluted), so we have to chose between: 1. support concurrent tests / Lifecycle.PER_CLASS 2. support asynchronously published events and events assertions performed in a separate thread (like with Awaitility)

The @Autowired support is also problematic, because for PER_CLASS the ApplicationEvents is injected once and cannot have a dedicated instance per test method.

This can't be solved in time for 6.0.5, and in fact I'm not sure this can be solved at all without some breaking change / behavior (and as such 6.1.0 would potentially be a better candidate). Removing from milestone and marking for further team discussion / design.

Comment From: simonbasle

I've made quite the number of local iterations on this one, and I think the minimum viable implementation is one that:

  1. have SpringExtension detect and reject test cases annotated with @RecordApplicationEvents and @TestInstance(Lifecycle.PER_CLASS) and @Execution(ExecutionMode.CONCURRENT)
  2. have ApplicationEventsHolder use an InheritableThreadLocal<ApplicationEvents>

I've tried various other configurations, none of which really alleviate the PER_CLASS caveat. These other ideas included: storing an ApplicationEvents in the TestContext, attempting to register one ApplicationEventApplicationListener per test and to trigger autowiring again, filtering captured events in the listener if a different ApplicationEvents could be found in the holder vs the one with which the event listener was constructed...

In the end, the InheritableThreadLocal seems to have the same impact with the minimum of code.

Comment From: simonbasle

Superseded by gh-30020