The issue with ApplicationEventsHolder and its InheritableThreadLocal solution implemented in #30020 is that it only works if the thread is spawned in the test method like in the unit test JUnit4ApplicationEventsAsyncIntegrationTests but if the thread is spawned before registerApplicationEvents is called on the main/parent thread (for instance during application context creation), then every application event emitted during the test on this spawned thread will be ignored.

Here's a slightly modified version of JUnit4ApplicationEventsAsyncIntegrationTests that reproduce the issue:

@SpringBootTest
@RecordApplicationEvents
public class JUnit4ApplicationEventsAsyncIntegrationTests {
    @Autowired
    ApplicationEvents applicationEvents;

    @Autowired
    Thread thread;

    @Test
    public void asyncPublication() throws InterruptedException {
        thread.start();
        thread.join();

        assertThat(this.applicationEvents.stream(CustomEvent.class)).singleElement()
                .extracting(CustomEvent::getMessage, InstanceOfAssertFactories.STRING).isEqualTo("async");
    }

    @Configuration
    static class Config {
        @Autowired
        ApplicationContext context;

        @Bean
        Thread thread() {
            return new Thread(() -> context.publishEvent(new CustomEvent("async")));
        }
    }

    static class CustomEvent extends ApplicationEvent {
        private static final long serialVersionUID = 1L;

        private final String message;

        CustomEvent(String message) {
            super(message);
            this.message = message;
        }

        String getMessage() {
            return message;
        }
    }
}

Comment From: simonbasle

I don't think there is a perfect solution for your use case beyond what was introduced in #30020.

Perhaps the scope of @RecordApplicationEvents should be clarified to state it only includes events published from the unit test's thread or any thread that is a child of the unit test thread?

As a possible workaround however, maybe you could have your @Configuration class instantiate a Runnable instead of a Thread? That way, your test method can spawn the Thread itself, approximating the production behavior with a working recording during the test.

Comment From: romainmoreau

Unfortunately my real use case was a @Bean that creates threads during its instanciation and that is outside of my scope (provided by a library).

I finally moved on from @RecordApplicationEvents and used instead a simple @Component with an @EventListener method that records events.

Comment From: simonbasle

yeah that makes sense that the real setup is a little bit more complex.

the test recording of events is aiming at capturing events specific to a given test, first and foremost, in order to let users assert a shorter scope of test-fired events exhaustively without being overwhelmed by unrelated events. glad you found a creative workaround solution for your test.

I've added a short note in the javadoc of @RecordApplicationEvents to clarify a bit however, see 42874178ceb3381ecc6241ec0cf8e9c512506b1a.

Comment From: joshiste

I just ran into the same issue. Now rolling my own version of this, which I think is a pity.