SpringExtension for JUnit 5: Why Do We Need It?

SpringExtension integrates the Spring TestContext Framework into JUnit 5 Jupiter extension model, sets up the ApplicationContext, and manages dependencies.

In JUnit 5, an extension is a class that JUnit calls at defined points of a test run, and SpringExtension is the extension that connects the Spring TestContext Framework to those points. We register it with @ExtendWith(SpringExtension.class) at the top of a test class, so the test can use the Spring application context, dependency injection and Spring-managed transactions. Spring Boot test slice annotations such as @SpringBootTest and @WebMvcTest already include it, so we add the declaration ourselves only in tests that use none of them. The sections below cover the JUnit extension points, the tasks SpringExtension performs, its test execution listeners and the annotations that include it.

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

JUnit Jupiter extensions are classes or modules that extend the behavior of the JUnit 5 testing framework. They add features, customizations or integrations to our JUnit tests.

JUnit calls an extension at certain events of a test run, such as initialization post-processing, parameter resolution or lifecycle callbacks. Each of these events is an extension point. When such an event occurs, the JUnit engine calls the registered extensions for that event.

Each extension covers a core task of the library it is written for, as these examples show.

  • MockitoExtension integrates Mockito with JUnit 5 for mocking and stubbing dependencies.
  • TestcontainersExtension starts and stops the Docker containers of Testcontainers in integration tests.
  • RestAssuredExtension integrates JUnit 5 with RestAssured, which provides assertions and methods to send HTTP requests and validate responses in API tests.
  • WireMockExtension integrates JUnit 5 with WireMock to stub and mock HTTP services.
  • DockerComposeExtension uses Docker Compose to start and stop multi-container applications in integration tests.
  • WebTestClientExtension tests and validates 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

The following example is a simple JUnit 5 test that injects the Spring application context with @Autowired and asserts that it is not null.

public class HelloTest {

  @Autowired
  ApplicationContext context;

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

When we run the test, it fails, because JUnit knows nothing about the Spring framework or its application context.

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

The class SpringExtension is provided by the Spring Framework and integrates Spring with JUnit. It sets up and manages the Spring application context, dependency injection and transactional behavior during tests.

We modify the previous test class and add the declaration @ExtendWith(SpringExtension.class).

@ExtendWith(SpringExtension.class)
public class HelloTest {

  @Autowired
  ApplicationContext context;

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

When we rerun the test, it succeeds. JUnit now has access to the Spring application context and its features.

3. What Else SpringExtension Does?

Apart from setting up the Spring application context, SpringExtension handles dependency injection, transaction management and the reporting of test failures, as the table shows.

FeatureResponsibilities
Dependency InjectionIt injects Spring-managed dependencies into test classes that use annotations such as @Autowired, @Value and @Qualifier.
Transaction ManagementIt sets up and manages transactions for tests that use database operations. It starts a transaction before each test method and rolls back the changes after it, so the database is in a consistent state for the next test.
Exception Handling and ReportingIf a test method fails, it provides error messages and stack traces that help us identify the cause of the failure.

4. Test Execution Listeners

Test execution listeners extend the functionality of SpringExtension. They are registered with the testing framework, in most cases as Spring beans.

During the test lifecycle, the JUnit Platform and SpringExtension invoke the registered test execution listeners at specific points, such as before the test class setup, before a test method and after a test method. Each listener runs the custom actions that its callback methods define.

First, Spring sets up the context. Then the listeners run their callbacks and the test methods execute, and afterward the listeners do any teardown or cleanup.

The Spring TestContext Framework registers seven test execution listeners by default, and each one handles a separate task.

  • ApplicationEventsTestExecutionListener adds support for testing application events through an ApplicationEvents helper, and it is effective only if the test class 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 runs the SQL before the test starts.
  • TransactionalTestExecutionListener handles the @Transactional annotation in test cases and rolls back the transaction at the end of a test.
  • ServletTestExecutionListener loads a web application context when it detects the @WebAppConfiguration annotation.

5. How to Include SpringExtension in a Test Class?

We can add @ExtendWith(SpringExtension.class) directly at the top of a test class. Several other annotations include it as a meta-annotation, so adding one of them to the test class registers the extension too.

For example, the source of the @WebMvcTest annotation contains @ExtendWith({SpringExtension.class}) next to other annotations for its specific functionality.

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

Besides @WebMvcTest, the other Spring Boot test slice annotations and the Spring JUnit config annotations already include it.

If a test uses a test slice annotation, we do not add the @ExtendWith({SpringExtension.class}) statement again. The duplicate statement provides no value, and when only some test classes have it, it confuses new developers.

6. Conclusion

SpringExtension connects JUnit 5 to the Spring TestContext Framework for tests of Spring applications. It sets up the Spring context, injects dependencies and manages test transactions. A test that uses a test slice annotation does not need the explicit declaration.

Happy Learning !!

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.