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.
@ExtendWith(RandomParametersExtension.class)
@Test
void test(@Random int i) {
// ...
}
To register a custom RandomParametersExtension
for all tests in a particular class and its subclasses, you would annotate the test class as follows.
@ExtendWith(RandomParametersExtension.class)
class MyTests {
// ...
}
Multiple extensions can be registered together like this:
@ExtendWith({ DatabaseExtension.class, WebServerExtension.class })
class MyFirstTests {
// ...
}
As an alternative, multiple extensions can be registered separately like this:
@ExtendWith(DatabaseExtension.class)
@ExtendWith(WebServerExtension.class)
class MySecondTests {
// ...
}
Extension Registration Order Extensions registered declaratively via |
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:
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@ExtendWith({ DatabaseExtension.class, WebServerExtension.class })
public @interface DatabaseAndWebServerExtension {
}
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 Any |
@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
class WebServerDemo {
@RegisterExtension
static WebServerExtension server = WebServerExtension.builder()
.enableSecurity(false)
.build();
@Test
void getProductList() {
WebClient webClient = new WebClient();
String serverUrl = server.getServerUrl();
// Use WebClient to connect to web server using serverUrl and verify response
assertEquals(200, webClient.get(serverUrl + "/products").getResponseStatus());
}
}
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
class KotlinWebServerDemo {
companion object {
@JvmField
@RegisterExtension
val server = WebServerExtension.builder()
.enableSecurity(false)
.build()
}
@Test
fun getProductList() {
// Use WebClient to connect to web server using serverUrl and verify response
val webClient = WebClient()
val serverUrl = server.serverUrl
assertEquals(200, webClient.get("$serverUrl/products").responseStatus)
}
}
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
class DocumentationDemo {
static Path lookUpDocsDir() {
// return path to docs dir
}
@RegisterExtension
DocumentationExtension docs = DocumentationExtension.forPath(lookUpDocsDir());
@Test
void generateDocumentation() {
// use this.docs ...
}
}
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.