5.2. Registering Extensions

Extensions can be registered declaratively via @ExtendWith, programmatically via @RegisterExtension, or automatically via Java’s ServiceLoader mechanism.

5.2.1. Declarative Extension Registration

Developers can register one or more extensions declaratively by annotating a test interface, test class, test method, or custom composed annotation with @ExtendWith(…​) and supplying class references for the extensions to register.

For example, to register a custom RandomParametersExtension for a particular test method, you would annotate the test method as follows.

  1. @ExtendWith(RandomParametersExtension.class)
  2. @Test
  3. void test(@Random int i) {
  4. // ...
  5. }

To register a custom RandomParametersExtension for all tests in a particular class and its subclasses, you would annotate the test class as follows.

  1. @ExtendWith(RandomParametersExtension.class)
  2. class MyTests {
  3. // ...
  4. }

Multiple extensions can be registered together like this:

  1. @ExtendWith({ DatabaseExtension.class, WebServerExtension.class })
  2. class MyFirstTests {
  3. // ...
  4. }

As an alternative, multiple extensions can be registered separately like this:

  1. @ExtendWith(DatabaseExtension.class)
  2. @ExtendWith(WebServerExtension.class)
  3. class MySecondTests {
  4. // ...
  5. }
Extension Registration Order

Extensions registered declaratively via @ExtendWith will be executed in the order in which they are declared in the source code. For example, the execution of tests in both MyFirstTests and MySecondTests will be extended by the DatabaseExtension and WebServerExtension, in exactly that order.

If you wish to combine multiple extensions in a reusable way, you can define a custom composed annotation and use @ExtendWith as a meta-annotation:

  1. @Target({ ElementType.TYPE, ElementType.METHOD })
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @ExtendWith({ DatabaseExtension.class, WebServerExtension.class })
  4. public @interface DatabaseAndWebServerExtension {
  5. }

5.2.2. Programmatic Extension Registration

Developers can register extensions programmatically by annotating fields in test classes with [@RegisterExtension](https://junit.org/junit5/docs/current/api/org.junit.jupiter.api/org/junit/jupiter/api/extension/RegisterExtension.html).

When an extension is registered declaratively via @ExtendWith, it can typically only be configured via annotations. 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.

Extension Registration Order

By default, extensions registered programmatically via @RegisterExtension will be ordered using an algorithm that is deterministic but intentionally nonobvious. This ensures that subsequent runs of a test suite execute extensions in the same order, thereby allowing for repeatable builds. However, there are times when extensions need to be registered in an explicit order. To achieve that, annotate @RegisterExtension fields with @Order.

Any @RegisterExtension field not annotated with @Order will be ordered using the default order which has a value of Integer.MAX_VALUE / 2. This allows @Order annotated extension fields to be explicitly ordered before or after non-annotated extension fields. Extensions with an explicit order value less than the default order value will be registered before non-annotated extensions. Similarly, extensions with an explicit order value greater than the default order value will be registered after non-annotated extensions. For example, assigning an extension an explicit order value that is greater than the default order value allows before callback extensions to be registered last and after callback extensions to be registered first, relative to other programmatically registered extensions.

@RegisterExtension fields must not be private or null (at evaluation time) but may be either static or non-static.
Static Fields

If a @RegisterExtension field is static, the extension will be registered after extensions that are registered at the class level via @ExtendWith. Such static extensions are not limited in which extension APIs they can implement. Extensions registered via static fields may therefore implement class-level and instance-level extension APIs such as BeforeAllCallback, AfterAllCallback, TestInstancePostProcessor, and TestInstancePreDestroyCallback as well as method-level extension APIs such as BeforeEachCallback, etc.

In the following example, the server field in the test class is initialized programmatically by using a builder pattern supported by the WebServerExtension. The configured WebServerExtension will be automatically registered as an extension at the class level — for example, in order to start the server before all tests in the class and then stop the server after all tests in the class have completed. In addition, static lifecycle methods annotated with @BeforeAll or @AfterAll as well as @BeforeEach, @AfterEach, and @Test methods can access the instance of the extension via the server field if necessary.

An extension registered via a static field

  1. class WebServerDemo {
  2. @RegisterExtension
  3. static WebServerExtension server = WebServerExtension.builder()
  4. .enableSecurity(false)
  5. .build();
  6. @Test
  7. void getProductList() {
  8. WebClient webClient = new WebClient();
  9. String serverUrl = server.getServerUrl();
  10. // Use WebClient to connect to web server using serverUrl and verify response
  11. assertEquals(200, webClient.get(serverUrl + "/products").getResponseStatus());
  12. }
  13. }
Static Fields in Kotlin

The Kotlin programming language does not have the concept of a static field. However, the compiler can be instructed to generate static fields using annotations. Since, as stated earlier, @RegisterExtension fields must not be private nor null, one cannot use the @JvmStatic annotation in Kotlin as it generates private fields. Rather, the @JvmField annotation must be used.

The following example is a version of the WebServerDemo from the previous section that has been ported to Kotlin.

Registering an extension via a static field in Kotlin

  1. class KotlinWebServerDemo {
  2. companion object {
  3. @JvmField
  4. @RegisterExtension
  5. val server = WebServerExtension.builder()
  6. .enableSecurity(false)
  7. .build()
  8. }
  9. @Test
  10. fun getProductList() {
  11. // Use WebClient to connect to web server using serverUrl and verify response
  12. val webClient = WebClient()
  13. val serverUrl = server.serverUrl
  14. assertEquals(200, webClient.get("$serverUrl/products").responseStatus)
  15. }
  16. }
Instance Fields

If a @RegisterExtension field is non-static (i.e., an instance field), the extension will be registered after the test class has been instantiated and after each registered TestInstancePostProcessor has been given a chance to post-process the test instance (potentially injecting the instance of the extension to be used into the annotated field). Thus, if such an instance extension implements class-level or instance-level extension APIs such as BeforeAllCallback, AfterAllCallback, or TestInstancePostProcessor, those APIs will not be honored. By default, an instance extension will be registered after extensions that are registered at the method level via @ExtendWith; however, if the test class is configured with @TestInstance(Lifecycle.PER_CLASS) semantics, an instance extension will be registered before extensions that are registered at the method level via @ExtendWith.

In the following example, the docs field in the test class is initialized programmatically by invoking a custom lookUpDocsDir() method and supplying the result to the static forPath() factory method in the DocumentationExtension. The configured DocumentationExtension will be automatically registered as an extension at the method level. In addition, @BeforeEach, @AfterEach, and @Test methods can access the instance of the extension via the docs field if necessary.

An extension registered via an instance field

  1. class DocumentationDemo {
  2. static Path lookUpDocsDir() {
  3. // return path to docs dir
  4. }
  5. @RegisterExtension
  6. DocumentationExtension docs = DocumentationExtension.forPath(lookUpDocsDir());
  7. @Test
  8. void generateDocumentation() {
  9. // use this.docs ...
  10. }
  11. }

5.2.3. Automatic Extension Registration

In addition to declarative extension registration and programmatic extension registration support using annotations, JUnit Jupiter also supports global extension registration via Java’s java.util.ServiceLoader mechanism, allowing third-party extensions to be auto-detected and automatically registered based on what is available in the classpath.

Specifically, a custom extension can be registered by supplying its fully qualified class name in a file named org.junit.jupiter.api.extension.Extension within the /META-INF/services folder in its enclosing JAR file.

Enabling Automatic Extension Detection

Auto-detection is an advanced feature and is therefore not enabled by default. To enable it, set the junit.jupiter.extensions.autodetection.enabled configuration parameter to true. This can be supplied as a JVM system property, as a configuration parameter in the LauncherDiscoveryRequest that is passed to the Launcher, or via the JUnit Platform configuration file (see Configuration Parameters for details).

For example, to enable auto-detection of extensions, you can start your JVM with the following system property.

-Djunit.jupiter.extensions.autodetection.enabled=true

When auto-detection is enabled, extensions discovered via the ServiceLoader mechanism will be added to the extension registry after JUnit Jupiter’s global extensions (e.g., support for TestInfo, TestReporter, etc.).

5.2.4. Extension Inheritance

Registered extensions are inherited within test class hierarchies with top-down semantics. Similarly, extensions registered at the class-level are inherited at the method-level. Furthermore, a specific extension implementation can only be registered once for a given extension context and its parent contexts. Consequently, any attempt to register a duplicate extension implementation will be ignored.