Record Spring Events When Testing Spring Boot Applications

Last Updated:  August 27, 2021 | Published: February 2, 2021

One of the core features of Spring is the event publishing functionality. We can use events to decouple parts of our application and implement a publish-subscribe pattern. One part of our application can publish an event that multiple listeners (even asynchronously) react to. As part of Spring Framework 5.3.3 (Spring Boot 2.4.2), we can now record and verify all published events (ApplicationEvent) when testing Spring Boot applications using @RecrodApplicationEvents.

Setup To Record an ApplicationEvent with Spring Boot

To use this feature, we only need the Spring Boot Starter Test that is part of every Spring Boot project you bootstrap at start.spring.io.

Make sure to use a Spring Boot version >= 2.4.2 as we need a Spring Framework version >= 5.3.3.

There is one additional requirement for our tests: we need to work with a Spring TestContext as event publishing is a core functionality of the ApplicationContext.

Hence it doesn't work for a unit test where no Spring TestContext framework support is used. There are multiple Spring Boot test slice annotations that conveniently bootstrap the context for our test.

Introduction To Spring Event Publishing

As an example, we'll test a Java class that emits a UserCreationEvent whenever we successfully create a new user. The event includes metadata about the user that is relevant for subsequent tasks:

As of Spring Framework 4.2, we don't have to extend the abstract ApplicationEvent class and can use any POJO as our event class. Refer to this article for a great introduction to application events with Spring Boot.

Our UserService creates and stores our new users. We can create either a single user or a batch of users:

Once the user is part of our system, we'll notify other components of our application by publishing a UserCreationEvent.

As an example, our application performs two additional operations whenever we fire such an UserCreationEvent:

Record And Verify ApplicationEvents With Spring Boot

Let's write our first test that ensures the UserService emits an event whenever we create a new user. We instruct Spring to capture our events using the @RecordApplicationEvents annotation on top of our test class:

After we execute the public method of our class under test (createUser of the UserService in this example), we can request all captured events from the ApplicationEvents bean that we inject to our test.

The public .stream() method of the ApplicationEvents class allows iterating over all recorded events for a test. There's an overloaded version of .stream() where we request a stream of only specific events.

Even though we're only emitting one event from our application, Spring captures four events for the test above. The remaining three events are Spring specific like PrepareInstanceEvent from the TestContext framework.

As we're using the JUnit Jupiter and the SpringExtension (registered for us when using @SpringBootTest), we can also inject the ApplicationEvents bean to a JUnit lifecycle method or directly to a test:

The instance of ApplicationEvents is created before and removed after each test as part of the current thread. Hence you can even use field injection and @TestInstance(TestInstance.Lifecycle.PER_CLASS) to share the test instance between multiple tests (PER_METHOD is the default).

Please note that it might be overkill to start the whole Spring Context using @SpringBootTest for such a test. We could also write a test that populates a minimal Spring TestContext with just our UserService bean to verify that our UserCreationEvent is published:

… or use an alternative testing approach.

Alternatives to Testing Spring Events

Depending on what you want to achieve with your test, it might be sufficient enough to verify this functionality with a unit test:

Note that we're not using any Spring Test support here and relying solely on Mockito and JUnit Jupiter.

Another approach would be to not explicitly verify this technical detail (publishing events) and verify the whole use case with an integration test:

In this case, we would need to verify the outcome of our event listeners and e.g. check that we put a message to a queue or incrementing a counter.

Summary Of Testing Spring Events With Spring Boot

All different approaches boil down to behavior vs. state testing. With this new @RecordApplicationEvents feature of Spring Test, we might be tempted to do more behavior testing and verify the internals of our implementation. In general, we should focus on state (aka. outcome) testing as this supports hassle-free refactorings.

Imagine the following: We use anApplicationEvent to decouple parts of our application and ensure that this event is fired during a test. Two weeks later, we decide to remove/rework this decoupling (for whatever reasons). Our use case might still work as expected, but our test now fails because we're making assumptions about the technical implementation by verifying how many events we published.

Keep this in mind and not overspecify your tests with details about the implementation (if you want to refactor in the future :D). Nevertheless, there are for sure test cases where this @RecordApplicationEvents feature helps a lot.

The source code with all test alternatives for this Spring Event testing with Spring Boot is available on GitHub.

Have fun testing your Spring Events,

Philip

>