tl;dr: How can I instantiate a custom data provider as a Spring component before all tests run?
Is there a smart way to inject Spring components into a custom JUnit Jupiter extension that implements BeforeAllCallback
? The beforeAll
method should trigger a complex process before MyTestClass
is executed with @ExtendWith(OncePerTestRunExtension.class)
.
I created a Spring Boot Application (src/main/java
) that provides my test (src/test/java
) with the necessary data. The data can take up to a few hours to be prepared for the tests. It also gives me abstracted access to some rest-endpoints.
The data does not change in between the process of all test classes. So I just want to pull the data once.
Writing all tests in just one class would work, but I think the separation into different classes gives a better overview.
In contrast, when an extension is registered via @RegisterExtension , it can be configured programmatically — for example, in order to pass arguments to the extension's constructor, a static factory method, or a builder API.
JUnit 5 extensions are related to a certain event in the execution of a test, referred to as an extension point. When a certain life cycle phase is reached, the JUnit engine calls registered extensions. Five main types of extension points can be used: test instance post-processing. conditional test execution.
@ExtendWith is a repeatable annotation that is used to register extensions for the annotated test class, test interface, test method, parameter, or field. Annotated parameters are supported in test class constructors, in test methods, and in @BeforeAll , @AfterAll , @BeforeEach , and @AfterEach lifecycle methods.
Class SpringExtension. SpringExtension integrates the Spring TestContext Framework into JUnit 5's Jupiter programming model. To use this extension, simply annotate a JUnit Jupiter based test class with @ExtendWith(SpringExtension. class) , @SpringJUnitConfig , or @SpringJUnitWebConfig .
In the beforeAll(ExtensionContext)
method of your custom BeforeAllCallback
, you can access the Spring ApplicationContext
for the current test class via SpringExtension.getApplicationContext(extensionContext)
.
If you configure your custom data provider as a Spring component in that ApplicationContext
, you can then retrieve the component from the ApplicationContext
within your extension -- for example, via applicationContext.getBean(MyDataProvider.class)
.
If you need to process the data and store that processed data between tests, you can store that in the root ExtensionContext.Store
in JUnit Jupiter. See ExtensionContext.getRoot()
and the getOrComputeIfAbsent(...)
variants in ExtensionContext.Store
for details.
Here's how I implemented this to setup some test data in my database using a dataSource
Spring bean, by combining Sam's answer above with this answer: https://stackoverflow.com/a/51556718/278800
import javax.sql.DataSource;
import org.junit.jupiter.api.extension.BeforeAllCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.springframework.context.ApplicationContext;
import org.springframework.test.context.junit.jupiter.SpringExtension;
public class TestDataSetup implements BeforeAllCallback, ExtensionContext.Store.CloseableResource {
private static boolean started = false;
private DataSource dataSource;
@Override
public void beforeAll(ExtensionContext extensionContext) {
synchronized (TestDataSetup.class) {
if (!started) {
started = true;
// get the dataSource bean from the spring context
ApplicationContext springContext = SpringExtension.getApplicationContext(extensionContext);
this.dataSource = springContext.getBean(DataSource.class);
// TODO: put your one-time db initialization code here
// register a callback hook for when the root test context is shut down
extensionContext
.getRoot()
.getStore(ExtensionContext.Namespace.GLOBAL)
.put("TestDataSetup-started", this);
}
}
}
@Override
public void close() {
synchronized (TestDataSetup.class) {
// TODO: put your db cleanup code here
}
}
(I'm not 100% sure on the thread safety of this, so I added the synchronized
block just to be safe.)
To enable this extension, you just need to add this annotation to your test classes which need it:
@ExtendWith(TestDataSetup.class)
The nice thing is that Junit 5 allows multiple extensions, so this works even if your tests are already annotated with @ExtendWith(SpringExtension.class)
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With