Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JUnit 5: Inject spring components to Extension (BeforeAllCallback / AfterAllCallback)

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.

like image 431
froehli Avatar asked Jul 05 '19 14:07

froehli


People also ask

How do I add parameters to a registered extension?

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.

What JUnit 5 extensions?

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.

What is the use of ExtendWith annotation?

@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.

What is the use of @ExtendWith SpringExtension class?

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 .


2 Answers

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.

like image 114
Sam Brannen Avatar answered Sep 27 '22 16:09

Sam Brannen


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).

like image 40
Steve K Avatar answered Sep 27 '22 17:09

Steve K