Skip to content

Introduce ApplicationEvents abstraction to capture application events published during a test#25616

Closed
odrotbohm wants to merge 1 commit intospring-projects:masterfrom
odrotbohm:feature/published-events
Closed

Introduce ApplicationEvents abstraction to capture application events published during a test#25616
odrotbohm wants to merge 1 commit intospring-projects:masterfrom
odrotbohm:feature/published-events

Conversation

@odrotbohm
Copy link
Member

PublishedEventsExtension registers a composite ApplicationListener backed by thread-bound individual listeners to capture all application events published during the execution of a test method. Those would declare a PublishedEvents parameter to the method which provides API to define assertions based on the events published:

@ExtendWith(PublishedEventsExtension.class)
class SampleTests {

  @Test
  void someTestMethod(PublishedEvents events) {

    // Filter events by type and predicate
    assertThat(events.ofType(MyEventType.class).matching(it -> …)).hasSize(2);
  }
}

@odrotbohm odrotbohm requested a review from sbrannen August 19, 2020 20:26
@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged or decided on label Aug 19, 2020
@sbrannen sbrannen added in: test Issues in the test module type: enhancement A general enhancement labels Aug 20, 2020
@sbrannen sbrannen self-assigned this Aug 20, 2020
@sbrannen
Copy link
Member

Thanks for the PR!

Tentatively slated for 5.3 RC1 for review and potential inclusion in 5.3

@sbrannen sbrannen added this to the 5.3 RC1 milestone Aug 20, 2020
@jhoeller jhoeller modified the milestones: 5.3 RC1, 5.3 RC2 Aug 25, 2020
…d during a test method.

PublishedEventsExtension registers a composite ApplicationListener backed by thread-bound individual listeners to capture all application events published during the execution of a test method. Those would declare a PublishedEvents parameter to the method which provides API to define assertions based on the events published:

@ExtendWith(PublishedEventsExtension.class)
class SampleTests {

  @test
  void someTestMethod(PublishedEvents events) {

    // Filter events by type and predicate
    assertThat(events.ofType(MyEventType.class).matching(it -> …)).hasSize(2);
  }
}
@odrotbohm odrotbohm force-pushed the feature/published-events branch from 5170eca to 3eab764 Compare August 31, 2020 06:16
@jhoeller jhoeller removed the status: waiting-for-triage An issue we've not yet triaged or decided on label Sep 28, 2020
@sbrannen sbrannen modified the milestones: 5.3 RC2, 5.3 GA Oct 13, 2020
@sbrannen sbrannen modified the milestones: 5.3 GA, 5.3.1 Oct 26, 2020
@sbrannen sbrannen modified the milestones: 5.3.1, 5.3.2 Nov 9, 2020
@sbrannen
Copy link
Member

sbrannen commented Nov 9, 2020

The current proposal introduces support for the following abstraction.

/**
 * {@code ApplicationEvents} encapsulates all {@linkplain ApplicationEvent
 * application events} that were fired during the execution of a single test
 * method.
 *
 * @author Sam Brannen
 * @author Oliver Drotbohm
 * @since 5.3.1
 * @see ApplicationEventsExtension
 * @see org.springframework.context.ApplicationEvent
 * @see org.springframework.context.ApplicationListener
 */
public interface ApplicationEvents {

	/**
	 * Stream all application events that were fired during test execution.
	 * @return a stream of all application events
	 */
	Stream<ApplicationEvent> stream();

	/**
	 * Stream all application events or event payloads of the given type that
	 * were fired during test execution.
	 * @param <T> the event type
	 * @param type the type of events or payloads to stream; never {@code null}
	 * @return a stream of all application events or event payloads of the
	 * specified type
	 */
	<T> Stream<T> stream(Class<T> type);

}

Current work on this issue can be viewed in the following branch that builds on top of this PR: master...sbrannen:issues/gh-25616-application-events-extension

Tentatively slated for inclusion in 5.3.2.

@sbrannen sbrannen modified the milestones: 5.3.2, 5.3.3 Dec 7, 2020
sbrannen added a commit to sbrannen/spring-framework that referenced this pull request Dec 15, 2020
This reworks the ApplicationEvents support so that it is based solely
on a custom TestExecutionListener instead of a ContextCustomizer and a
TestExecutionListener.

See spring-projectsgh-25616
sbrannen added a commit to sbrannen/spring-framework that referenced this pull request Dec 15, 2020
This reworks the ApplicationEvents support so that the current
ApplicationEvents instance can be @Autowired into the test class for
use with testing frameworks other than JUnit Jupiter such as JUnit 4
and TestNG.

See spring-projectsgh-25616
sbrannen added a commit to sbrannen/spring-framework that referenced this pull request Dec 15, 2020
Now that the current ApplicationEvents instance can be @Autowired into
the test class, we no longer have a concrete need for a dedicated
JUnit Jupiter ParameterResolver implementation since the SpringExtension
for JUnit Jupiter already supports autowiring of beans from the test's
ApplicationContext into test constructors, lifecycle methods, and test
methods.

See spring-projectsgh-25616
sbrannen added a commit to sbrannen/spring-framework that referenced this pull request Dec 15, 2020
…piter

This commit modifies the SpringExtension so that parameters of type
ApplicationEvents are considered autowired candidates without the
presence of @Autowired on the formal parameter declaration.

This is analogous to the existing support for injecting the
ApplicationContext without the parameter being annotated with
@Autowired.

See spring-projectsgh-25616
sbrannen added a commit to sbrannen/spring-framework that referenced this pull request Dec 15, 2020
sbrannen added a commit to sbrannen/spring-framework that referenced this pull request Dec 15, 2020
sbrannen added a commit to sbrannen/spring-framework that referenced this pull request Dec 15, 2020
sbrannen added a commit to sbrannen/spring-framework that referenced this pull request Dec 15, 2020
sbrannen added a commit to sbrannen/spring-framework that referenced this pull request Dec 15, 2020
This commit adds proper support for ApplicationEvents when the test
instance is shared -- for example, in TestNG or in JUnit Jupiter with
@testinstance(PER_CLASS) semantics.

See spring-projectsgh-25616
sbrannen added a commit to sbrannen/spring-framework that referenced this pull request Dec 15, 2020
sbrannen added a commit to sbrannen/spring-framework that referenced this pull request Dec 15, 2020
sbrannen added a commit to sbrannen/spring-framework that referenced this pull request Dec 15, 2020
sbrannen added a commit to sbrannen/spring-framework that referenced this pull request Dec 15, 2020
sbrannen added a commit to sbrannen/spring-framework that referenced this pull request Dec 15, 2020
As a proof of concept, this commit introduces scoped proxy support for
an ApplicationEvents bean that is managed by the SimpleThreadScope.

Although this approach works in general (in terms of providing an
ApplicationEvents bean that can be autowired into test classes and
retrieved as a bean by third-party extensions), it has a major
drawback. Specifically, an ApplicationEvents instance will be created
on-demand for the current thread whenever the
ThreadBoundApplicationListener attempts to access the
ApplicationEvents, and this will occur even if the user has not
annotated the test class with @RecordApplicationEvents.

In light of the above, this commit will likely be subsequently reverted
but remains in tact in the commit history for possible future
consideration.

See spring-projectsgh-25616
sbrannen added a commit to sbrannen/spring-framework that referenced this pull request Dec 15, 2020
sbrannen added a commit to sbrannen/spring-framework that referenced this pull request Dec 15, 2020
sbrannen added a commit to sbrannen/spring-framework that referenced this pull request Dec 15, 2020
sbrannen added a commit to sbrannen/spring-framework that referenced this pull request Dec 15, 2020
sbrannen added a commit to sbrannen/spring-framework that referenced this pull request Dec 18, 2020
sbrannen added a commit to sbrannen/spring-framework that referenced this pull request Dec 18, 2020
We currently do not support parameter resolution for ApplicationEvents
in a test class constructor. In light of that it is better to throw
an exception with an explicit error message rather than leave the user
confused by a misleading exception being thrown.

See spring-projectsgh-25616
sbrannen added a commit to sbrannen/spring-framework that referenced this pull request Dec 18, 2020
Prior to this commit, if multiple tests were being executed in parallel
for a test class with per-test-method test instance lifecycle semantics
(e.g., in JUnit 4 and the default in JUnit Jupiter), then a race
condition could occur when registering the
ApplicationEventsApplicationListener in the ApplicationContext.
Consequently, multiple instances of ApplicationEventsApplicationListener
would be registered resulting in duplicate tracked events in
ApplicationEvents.

This commit avoids this race condition via a synchronized block in
ApplicationEventsTestExecutionListener's
registerListenerAndResolvableDependencyIfNecessary() method.

See spring-projectsgh-25616
sbrannen added a commit to sbrannen/spring-framework that referenced this pull request Dec 18, 2020
This commit also ensures that ApplicationEventsTestExecutionListener is
registered in AbstractTransactionalJUnit4SpringContextTests and
AbstractTransactionalTestNGSpringContextTests.

See spring-projectsgh-25616
sbrannen added a commit to sbrannen/spring-framework that referenced this pull request Dec 20, 2020
@sbrannen sbrannen changed the title Add PublishedEvents abstraction to capture ApplicationEvents published during a test method. Introduce ApplicationEvents abstraction to capture application events published during a test Dec 20, 2020
@sbrannen sbrannen closed this in 1565f4b Dec 20, 2020
@odrotbohm
Copy link
Member Author

Lovely! Thanks for all the extra effort you put into this, @sbrannen! 🙇

@sbrannen
Copy link
Member

You're very welcome!

Thanks for the idea, the prototype, and for your patience. :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

in: test Issues in the test module type: enhancement A general enhancement

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants