Permalink
Switch branches/tags
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
584 lines (454 sloc) 25.2 KB

Extension Model

Overview

In contrast to the competing Runner, TestRule, and MethodRule extension points in JUnit 4, the JUnit Jupiter extension model consists of a single, coherent concept: the Extension API. Note, however, that Extension itself is just a marker interface.

Registering Extensions

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

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 {
	// ...
}
Note
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.

Programmatic Extension Registration

Developers can register extensions programmatically by annotating fields in test classes with {RegisterExtension}.

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.

Note
@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, and TestInstancePostProcessor 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
Unresolved directive in <stdin> - include::{testDir}/example/registration/WebServerDemo.java[tags=user_guide]
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
Unresolved directive in <stdin> - include::{kotlinTestDir}/example/registration/KotlinWebServerDemo.kt[tags=user_guide]
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
Unresolved directive in <stdin> - include::{testDir}/example/registration/DocumentationDemo.java[tags=user_guide]

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, simply 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 [running-tests-config-params] 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.).

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.

Conditional Test Execution

{ExecutionCondition} defines the Extension API for programmatic, conditional test execution.

An ExecutionCondition is evaluated for each container (e.g., a test class) to determine if all the tests it contains should be executed based on the supplied ExtensionContext. Similarly, an ExecutionCondition is evaluated for each test to determine if a given test method should be executed based on the supplied ExtensionContext.

When multiple ExecutionCondition extensions are registered, a container or test is disabled as soon as one of the conditions returns disabled. Thus, there is no guarantee that a condition is evaluated because another extension might have already caused a container or test to be disabled. In other words, the evaluation works like the short-circuiting boolean OR operator.

See the source code of {DisabledCondition} and {Disabled} for concrete examples.

Deactivating Conditions

Sometimes it can be useful to run a test suite without certain conditions being active. For example, you may wish to run tests even if they are annotated with @Disabled in order to see if they are still broken. To do this, simply provide a pattern for the junit.jupiter.conditions.deactivate configuration parameter to specify which conditions should be deactivated (i.e., not evaluated) for the current test run. The pattern 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 [running-tests-config-params] for details).

For example, to deactivate JUnit’s @Disabled condition, you can start your JVM with the following system property.

-Djunit.jupiter.conditions.deactivate=org.junit.*DisabledCondition

Pattern Matching Syntax

If the junit.jupiter.conditions.deactivate pattern consists solely of an asterisk (*), all conditions will be deactivated. Otherwise, the pattern will be used to match against the fully qualified class name (FQCN) of each registered condition. Any dot (.) in the pattern will match against a dot (.) or a dollar sign ($) in the FQCN. Any asterisk (*) will match against one or more characters in the FQCN. All other characters in the pattern will be matched one-to-one against the FQCN.

Examples:

  • *: deactivates all conditions.

  • org.junit.*: deactivates every condition under the org.junit base package and any of its subpackages.

  • *.MyCondition: deactivates every condition whose simple class name is exactly MyCondition.

  • *System*: deactivates every condition whose simple class name contains System.

  • org.example.MyCondition: deactivates the condition whose FQCN is exactly org.example.MyCondition.

Test Instance Factories

{TestInstanceFactory} defines the API for Extensions that wish to create test class instances.

Common use cases include acquiring the test instance from a dependency injection framework or invoking a static factory method to create the test class instance.

If no TestInstanceFactory is registered, the framework will simply invoke the sole constructor for the test class to instantiate it, potentially resolving constructor arguments via registered ParameterResolver extensions.

Extensions that implement TestInstanceFactory can be registered on test interfaces, top-level test classes, or @Nested test classes.

Warning

Registering multiple extensions that implement TestInstanceFactory for any single class will result in an exception being thrown for all tests in that class, in any subclass, and in any nested class. Note that any TestInstanceFactory registered in a superclass or enclosing class (i.e., in the case of a @Nested test class) is inherited. It is the user’s responsibility to ensure that only a single TestInstanceFactory is registered for any specific test class.

Test Instance Post-processing

{TestInstancePostProcessor} defines the API for Extensions that wish to post process test instances.

Common use cases include injecting dependencies into the test instance, invoking custom initialization methods on the test instance, etc.

For a concrete example, consult the source code for the {MockitoExtension} and the {SpringExtension}.

Parameter Resolution

{ParameterResolver} defines the Extension API for dynamically resolving parameters at runtime.

If a test constructor or a @Test, @RepeatedTest, @ParameterizedTest, @TestFactory, @BeforeEach, @AfterEach, @BeforeAll, or @AfterAll method accepts a parameter, the parameter must be resolved at runtime by a ParameterResolver. A ParameterResolver can either be built-in (see {TestInfoParameterResolver}) or registered by the user. Generally speaking, parameters may be resolved by name, type, annotation, or any combination thereof. For concrete examples, consult the source code for {CustomTypeParameterResolver} and {CustomAnnotationParameterResolver}.

Warning

Due to a bug in the byte code generated by javac on JDK versions prior to JDK 9, looking up annotations on parameters directly via the core java.lang.reflect.Parameter API will always fail for inner class constructors (e.g., a constructor in a @Nested test class).

The {ParameterContext} API supplied to ParameterResolver implementations therefore includes the following convenience methods for correctly looking up annotations on parameters. Extension authors are strongly encouraged to use these methods instead of those provided in java.lang.reflect.Parameter in order to avoid this bug in the JDK.

  • boolean isAnnotated(Class<? extends Annotation> annotationType)

  • Optional<A> findAnnotation(Class<A> annotationType)

  • List<A> findRepeatableAnnotations(Class<A> annotationType)

Test Lifecycle Callbacks

The following interfaces define the APIs for extending tests at various points in the test execution lifecycle. Consult the following sections for examples and the Javadoc for each of these interfaces in the {extension-api-package} package for further details.

  • {BeforeAllCallback}

    • {BeforeEachCallback}

      • {BeforeTestExecutionCallback}

      • {AfterTestExecutionCallback}

    • {AfterEachCallback}

  • {AfterAllCallback}

Note
Implementing Multiple Extension APIs
Extension developers may choose to implement any number of these interfaces within a single extension. Consult the source code of the {SpringExtension} for a concrete example.

Before and After Test Execution Callbacks

{BeforeTestExecutionCallback} and {AfterTestExecutionCallback} define the APIs for Extensions that wish to add behavior that will be executed immediately before and immediately after a test method is executed, respectively. As such, these callbacks are well suited for timing, tracing, and similar use cases. If you need to implement callbacks that are invoked around @BeforeEach and @AfterEach methods, implement BeforeEachCallback and AfterEachCallback instead.

The following example shows how to use these callbacks to calculate and log the execution time of a test method. TimingExtension implements both BeforeTestExecutionCallback and AfterTestExecutionCallback in order to time and log the test execution.

An extension that times and logs the execution of test methods
Unresolved directive in <stdin> - include::{testDir}/example/timing/TimingExtension.java[tags=user_guide]

Since the TimingExtensionTests class registers the TimingExtension via @ExtendWith, its tests will have this timing applied when they execute.

A test class that uses the example TimingExtension
Unresolved directive in <stdin> - include::{testDir}/example/timing/TimingExtensionTests.java[tags=user_guide]

The following is an example of the logging produced when TimingExtensionTests is run.

INFO: Method [sleep20ms] took 24 ms.
INFO: Method [sleep50ms] took 53 ms.

Exception Handling

{TestExecutionExceptionHandler} defines the API for Extensions that wish to handle exceptions thrown during test execution.

The following example shows an extension which will swallow all instances of IOException but rethrow any other type of exception.

An exception handling extension
Unresolved directive in <stdin> - include::{testDir}/example/exception/IgnoreIOExceptionExtension.java[tags=user_guide]

Providing Invocation Contexts for Test Templates

A {TestTemplate} method can only be executed when at least one {TestTemplateInvocationContextProvider} is registered. Each such provider is responsible for providing a Stream of {TestTemplateInvocationContext} instances. Each context may specify a custom display name and a list of additional extensions that will only be used for the next invocation of the {TestTemplate} method.

The following example shows how to write a test template as well as how to register and implement a {TestTemplateInvocationContextProvider}.

A test template with accompanying extension
Unresolved directive in <stdin> - include::{testDir}/example/TestTemplateDemo.java[tags=user_guide]

In this example, the test template will be invoked twice. The display names of the invocations will be “foo” and “bar” as specified by the invocation context. Each invocation registers a custom {ParameterResolver} which is used to resolve the method parameter. The output when using the ConsoleLauncher is as follows.

└─ testTemplate(String) ✔
   ├─ foo ✔
   └─ bar ✔

The {TestTemplateInvocationContextProvider} extension API is primarily intended for implementing different kinds of tests that rely on repetitive invocation of a test-like method albeit in different contexts — for example, with different parameters, by preparing the test class instance differently, or multiple times without modifying the context. Please refer to the implementations of [writing-tests-repeated-tests] or [writing-tests-parameterized-tests] which use this extension point to provide their functionality.

Keeping State in Extensions

Usually, an extension is instantiated only once. So the question becomes relevant: How do you keep the state from one invocation of an extension to the next? The ExtensionContext API provides a Store exactly for this purpose. Extensions may put values into a store for later retrieval. See the TimingExtension for an example of using the Store with a method-level scope. It is important to remember that values stored in an ExtensionContext during test execution will not be available in the surrounding ExtensionContext. Since ExtensionContexts may be nested, the scope of inner contexts may also be limited. Consult the corresponding Javadoc for details on the methods available for storing and retrieving values via the {ExtensionContext_Store}.

Note
ExtensionContext.Store.CloseableResource
An extension context store is bound to its extension context lifecycle. When an extension context lifecycle ends it closes its associated store. All stored values that are instances of CloseableResource are notified by an invocation of their close() method.

Supported Utilities in Extensions

The junit-platform-commons artifact exposes a package named {junit-platform-support-package} that contains maintained utility methods for working with annotations, classes, reflection, and classpath scanning tasks. TestEngine and Extension authors are encouraged to use these supported methods in order to align with the behavior of the JUnit Platform.

Annotation Support

AnnotationSupport provides static utility methods that operate on annotated elements (e.g., packages, annotations, classes, interfaces, constructors, methods, and fields). These include methods to check whether an element is annotated or meta-annotated with a particular annotation, to search for specific annotations, and to find annotated methods and fields in a class or interface. Some of these methods search on implemented interfaces and within class hierarchies to find annotations. Consult the Javadoc for {AnnotationSupport} for further details.

Class Support

ClassSupport provides static utility methods for working with classes (i.e., instances of java.lang.Class). Consult the Javadoc for {ClassSupport} for further details.

Reflection Support

ReflectionSupport provides static utility methods that augment the standard JDK reflection and class-loading mechanisms. These include methods to scan the classpath in search of classes matching specified predicates, to load and create new instances of a class, and to find and invoke methods. Some of these methods traverse class hierarchies to locate matching methods. Consult the Javadoc for {ReflectionSupport} for further details.

Modifier Support

ModifierSupport provides static utility methods for working with member and class modifiers — for example, to determine if a member is declared as public, private, abstract, static, etc. Consult the Javadoc for {ModifierSupport} for further details.

Relative Execution Order of User Code and Extensions

When executing a test class that contains one or more test methods, a number of extension callbacks are called in addition to the user-provided test and lifecycle methods. The following diagram illustrates the relative order of user-provided code and extension code.

extensions lifecycle
User code and extension code

User-provided test and lifecycle methods are shown in orange, with callback code provided by extensions shown in blue. The grey box denotes the execution of a single test method and will be repeated for every test method in the test class.

The following table further explains the twelve steps in the User code and extension code diagram.

Step Interface/Annotation Description

1

interface org.junit.jupiter.api.extension.BeforeAllCallback

extension code executed before all tests of the container are executed

2

annotation org.junit.jupiter.api.BeforeAll

user code executed before all tests of the container are executed

3

interface org.junit.jupiter.api.extension.BeforeEachCallback

extension code executed before each test is executed

4

annotation org.junit.jupiter.api.BeforeEach

user code executed before each test is executed

5

interface org.junit.jupiter.api.extension.BeforeTestExecutionCallback

extension code executed immediately before a test is executed

6

annotation org.junit.jupiter.api.Test

user code of the actual test method

7

interface org.junit.jupiter.api.extension.TestExecutionExceptionHandler

extension code for handling exceptions thrown during a test

8

interface org.junit.jupiter.api.extension.AfterTestExecutionCallback

extension code executed immediately after test execution and its corresponding exception handlers

9

annotation org.junit.jupiter.api.AfterEach

user code executed after each test is executed

10

interface org.junit.jupiter.api.extension.AfterEachCallback

extension code executed after each test is executed

11

annotation org.junit.jupiter.api.AfterAll

user code executed after all tests of the container are executed

12

interface org.junit.jupiter.api.extension.AfterAllCallback

extension code executed after all tests of the container are executed

In the simplest case only the actual test method will be executed (step 6); all other steps are optional depending on the presence of user code or extension support for the corresponding lifecycle callback. For further details on the various lifecycle callbacks please consult the respective Javadoc for each annotation and extension.