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.