SpringExtension for JUnit 5: Why Do We Need It?

When testing Spring applications, many times we write @ExtendWith(SpringExtension.class) at the top of the JUnit test class. Let’s understand the impact of this declaration on the tests and their life cycle. This article will explore the when, why, and how of the usage of this extension.

SpringExtension integrates the Spring TestContext Framework into JUnit 5’s Jupiter programming model.

In JUnit 4, we have the same capabilities with SpringRunner class.

1. A Brief Introduction to JUnit Jupiter Extensions

In layman’s terms, JUnit Jupiter Extensions are classes or modules that extend the behavior of JUnit 5‘s testing framework. They provide additional features, customizations, or integrations alongside your JUnit tests.

Technically, in code, JUnit 5 extensions are invoked at certain events (such as initialization post-processing, parameter resolution, or lifecycle callbacks) in the execution of a test, referred to as an extension point. When such an event occurs, the JUnit engine calls the registered extensions for that event.

In the real world, extensions are generally focused on core tasks specific to the libraries they are written for. Let’s look at a few extensions and their goals to understand them even better:

  • MockitoExtension: seamlessly integrate Mockito with JUnit 5 for mocking and stubbing dependencies.
  • TestcontainersExtension: simplifies the use of Testcontainers with JUnit 5. It helps in managing and orchestrating (set up and tear down) Docker containers in integration tests.
  • RestAssuredExtension: integrates JUnit 5 with RestAssured and simplifies API testing by providing assertions and methods to make HTTP requests and validate responses.
  • WireMockExtension: integrates JUnit 5 with WireMock and helps in stubbing and mocking HTTP services.
  • DockerComposeExtension: simplifies the use of Docker Compose for starting and stopping multi-container applications in integration tests.
  • WebTestClientExtension: provides a convenient way to test and validate reactive web endpoints in a Spring WebFlux application.
  • FlywayExtension: integrates JUnit 5 with Flyway, manages the database schema changes, and tests the database migrations.

2. Introduction to SpringExtension

Let us start with writing a very simple JUnit 5 test in which we simply try to access the Spring’s application context.

public class HelloTest {

  @Autowired
  ApplicationContext context;

  @Test
  void contextLoads(){
    Assertions.assertNotNull(context);   //FAILS
  }
}

When we run the test, it fails, obviously. JUnit does not know anything about the Spring framework or its application context.

org.opentest4j.AssertionFailedError: expected: not <null>

This is where SpringExtension class comes into the picture. The SpringExtension class is provided by the Spring Framework to enable integration between Spring and JUnit. It facilitates the setup and management of the Spring application context, dependency injection, and transactional behavior during tests.

Let’s modify the previous test class and add @ExtendWith(SpringExtension.class) declaration.

@ExtendWith(SpringExtension.class)
public class HelloTest {

  @Autowired
  ApplicationContext context;

  @Test
  void contextLoads(){
    Assertions.assertNotNull(context);   //PASS
  }
}

Now when we rerun the test, it succeeds without a problem. Now JUnit has access to Spring’s application context and its features.

3. What Else SpringExtension Does?

Apart from setting up the Spring application context, as discussed above, SpringExtension performs a lot of other tasks. Let’s explore them:

FeatureResponsibilities
Dependency InjectionIt facilitates dependency injection for the test classes when using annotations like @Autowired, @Value, @Qualifier, etc. to inject Spring-managed dependencies.
Transaction ManagementIt sets up and manages transactions for tests involving database operations. It automatically starts a transaction before each test method is executed, and rollback the changes leaving the database in a consistent state for the next test.
Exception Handling and ReportingIf a test method fails, it provides helpful error messages and stack traces, making it easier to identify the cause of the failure.

4. Test Execution Listeners

Test Execution Listeners expand the functionality provided by SpringExtension. They are registered with the testing framework, typically in the form of Spring beans.

During the test lifecycle, the JUnit Platform, along with the SpringExtension, invokes the registered Test Execution Listeners at specific points in the testing process, such as before test class setup, before test method execution, and after test method execution. The listeners are called by the testing framework to perform the custom actions and event handling defined in their callback methods.

The sequence of events typically involves setting up the Spring context, invoking the listeners based on the defined callbacks, executing the test methods, and then allowing the listeners to perform any necessary teardown or cleanup actions.

Spring TestContext framework provides a few test execution listeners that are registered by default:

  • ApplicationEventsTestExecutionListener: adds support for testing application events through the use of an ApplicationEvents helper. Only effective if the test class itself is annotated with @RecordApplicationEvents.
  • DependencyInjectionTestExecutionListener: injects dependencies, including the managed application context, into the tests.
  • DirtiesContextTestExecutionListener: handles the @DirtiesContext annotation and reloads the application context when necessary.
  • EventPublishingTestExecutionListener: publishes events for test execution through the Spring application event infrastructure.
  • SqlScriptsTestExecutionListener: detects @Sql annotations on the test and executes the SQL before the start of the test.
  • TransactionalTestExecutionListener: handles the @Transactional annotation in test cases and does a rollback at the end of a test.
  • ServletTestExecutionListener: handles the loading of a web application context when the @WebAppConfiguration annotation is detected.

5. How to Include SpringExtension in a Test Class?

Obviously, we can directly add the @ExtendWith(SpringExtension.class) at the top of a test class. Apart from this direct inclusion, a number of other meta-annotations include this automatically when we add them to the test class.

For example, if we peek inside the @WebMvcTest annotation then we can find @ExtendWith({SpringExtension.class}) included along with other functionality-specific annotations:

//...
@ExtendWith({SpringExtension.class})
//...
@AutoConfigureWebMvc
@AutoConfigureMockMvc
@ImportAutoConfiguration
public @interface WebMvcTest {
	
	//...
}

Similarly, all several other annotations along with the Spring Boot test slice annotations include this annotation already. Let’s list down these annotations:

If you are using a test slice annotation, do not explicitly add the @ExtendWith({SpringExtension.class}) statement. It is duplicate statement and provides no value. Rather it may create confusion for new developers, if used inconsistently.

6. Conclusion

The SpringExtension is the glue between JUnit 5 and the Spring Test Framework and helps in testing the Spring-based applications. It seamlessly sets up the Spring context, manages dependencies, and handles the transactions among other important tasks.

Happy Learning !!

Comments

Subscribe
Notify of
guest
0 Comments
Inline Feedbacks
View all comments

About Us

HowToDoInJava provides tutorials and how-to guides on Java and related technologies.

It also shares the best practices, algorithms & solutions and frequently asked interview questions.