Skip to content

Commit b8e44fa

Browse files
committed
Document support for @ExtendWith on parameters and fields
Issue: #864
1 parent e4804cb commit b8e44fa

File tree

6 files changed

+153
-50
lines changed

6 files changed

+153
-50
lines changed

documentation/src/docs/asciidoc/release-notes/release-notes-5.8.0-M2.adoc

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,9 @@ on GitHub.
5858
* Generating Java Flight Recorder events via module `org.junit.platform.jfr` is now also
5959
supported on Java 8 Update 262 or higher, in addition to Java 11 or later. See
6060
<<../user-guide/index.adoc#running-tests, Flight Recorder Support>> for details.
61+
* Suites executed by the `junit-platform-suite` module will now inherit the configuration
62+
parameters from the parent discovery request. This behavior can be disabled via the
63+
`@DisableParentConfigurationParameters` annotation.
6164

6265

6366
[[release-notes-5.8.0-M2-junit-jupiter]]
@@ -84,7 +87,11 @@ on GitHub.
8487

8588
==== New Features and Improvements
8689

87-
* `assertDoesNotThrow` now supports suspending functions when called from Kotlin.
90+
* New `assertThrowsExactly()` method in `Assertions` which is a more strict version of
91+
`assertThrows()` that allows you to assert that the exception thrown is of the exact
92+
type specified.
93+
* `assertDoesNotThrow()` in `Assertions` now supports suspending functions when called
94+
from Kotlin.
8895
* `@TempDir` can now be used to create multiple temporary directories. Instead of creating
8996
a single temporary directory per context (i.e. test class or method) every declaration
9097
of the `@TempDir` annotation on a field or method parameter now results in a separate
@@ -93,13 +100,15 @@ on GitHub.
93100
you can set the `junit.jupiter.tempdir.scope` configuration parameter to `per_context`.
94101
* `@TempDir` cleanup resets readable and executable permissions of the root temporary
95102
directory and any contained directories instead of failing to delete them.
96-
* `@TempDir` fields may now be private.
103+
* `@TempDir` fields may now be `private`.
104+
* `@ExtendWith` may now be used to register extensions declaratively via fields or
105+
parameters in test class constructors, test methods, and lifecycle methods. See
106+
<<../user-guide/index.adoc#extensions-registration-declarative, Declarative Extension
107+
Registration>> for details.
97108
* New `named()` static factory method in the `Named` interface that serves as an _alias_
98109
for `Named.of()`. `named()` is intended to be used via `import static`.
99110
* New `class` URI scheme for dynamic test sources. This allows tests to be located using
100111
the information available in a `StackTraceElement`.
101-
* New `assertThrowsExactly` method which is a more strict version of `assertThrows`
102-
that allows you to assert that the thrown exception has the exact specified class.
103112
* New `autoCloseArguments` attribute in `@ParameterizedTest` to close `AutoCloseable`
104113
arguments at the end of the test. This attribute defaults to true.
105114

@@ -117,6 +126,4 @@ on GitHub.
117126

118127
==== New Features and Improvements
119128

120-
* Suites executed by the `junit-platform-suite` module will now inherit the
121-
configuration parameters from the parent discovery request. This behaviour can
122-
be disabled via the `@DisableParentConfigurationParameters` annotation.
129+
* ❓

documentation/src/docs/asciidoc/user-guide/extensions.adoc

Lines changed: 98 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -22,28 +22,31 @@ Java's <<extensions-registration-automatic,`ServiceLoader`>> mechanism.
2222
Developers can register one or more extensions _declaratively_ by annotating a test
2323
interface, test class, test method, or custom _<<writing-tests-meta-annotations,composed
2424
annotation>>_ with `@ExtendWith(...)` and supplying class references for the extensions to
25-
register. As of JUnit Jupiter 5.8, `@ExtendWith` may also be declared on fields and on
25+
register. As of JUnit Jupiter 5.8, `@ExtendWith` may also be declared on fields or on
2626
parameters in test class constructors, in test methods, and in `@BeforeAll`, `@AfterAll`,
2727
`@BeforeEach`, and `@AfterEach` lifecycle methods.
2828

29-
For example, to register a custom `RandomParametersExtension` for a particular test
30-
method, you would annotate the test method as follows.
29+
For example, to register a `WebServerExtension` for a particular test method, you would
30+
annotate the test method as follows. We assume the `WebServerExtension` starts a local web
31+
server and injects the server's URL into parameters annotated with `@WebServerUrl`.
3132

3233
[source,java,indent=0]
3334
----
34-
@ExtendWith(RandomParametersExtension.class)
3535
@Test
36-
void test(@Random int i) {
37-
// ...
36+
@ExtendWith(WebServerExtension.class)
37+
void getProductList(@WebServerUrl String serverUrl) {
38+
WebClient webClient = new WebClient();
39+
// Use WebClient to connect to web server using serverUrl and verify response
40+
assertEquals(200, webClient.get(serverUrl + "/products").getResponseStatus());
3841
}
3942
----
4043

41-
To register a custom `RandomParametersExtension` for all tests in a particular class and
42-
its subclasses, you would annotate the test class as follows.
44+
To register the `WebServerExtension` for all tests in a particular class and its
45+
subclasses, you would annotate the test class as follows.
4346

4447
[source,java,indent=0]
4548
----
46-
@ExtendWith(RandomParametersExtension.class)
49+
@ExtendWith(WebServerExtension.class)
4750
class MyTests {
4851
// ...
4952
}
@@ -73,15 +76,16 @@ class MySecondTests {
7376
[TIP]
7477
.Extension Registration Order
7578
====
76-
Extensions registered declaratively via `@ExtendWith` will be executed in the order in
77-
which they are declared in the source code. For example, the execution of tests in both
78-
`MyFirstTests` and `MySecondTests` will be extended by the `DatabaseExtension` and
79-
`WebServerExtension`, **in exactly that order**.
79+
Extensions registered declaratively via `@ExtendWith` at the class level, method level, or
80+
parameter level will be executed in the order in which they are declared in the source
81+
code. For example, the execution of tests in both `MyFirstTests` and `MySecondTests` will
82+
be extended by the `DatabaseExtension` and `WebServerExtension`, **in exactly that order**.
8083
====
8184

8285
If you wish to combine multiple extensions in a reusable way, you can define a custom
8386
_<<writing-tests-meta-annotations,composed annotation>>_ and use `@ExtendWith` as a
84-
_meta-annotation_:
87+
_meta-annotation_ as in the following code listing. Then `@DatabaseAndWebServerExtension`
88+
can be used in place of `@ExtendWith({ DatabaseExtension.class, WebServerExtension.class })`.
8589

8690
[source,java,indent=0]
8791
----
@@ -92,6 +96,64 @@ public @interface DatabaseAndWebServerExtension {
9296
}
9397
----
9498

99+
The above examples demonstrate how `@ExtendWith` can be applied at the class level or at
100+
the method level; however, for certain use cases it makes sense for an extension to be
101+
registered declaratively at the field or parameter level. Consider a
102+
`RandomNumberExtension` that generates random numbers that can be injected into a field or
103+
via a parameter in a constructor, test method, or lifecycle method. If the extension
104+
provides a `@Random` annotation that is meta-annotated with
105+
`@ExtendWith(RandomNumberExtension.class)` (see listing below), the extension can be used
106+
transparently as in the following `RandomNumberTests` example.
107+
108+
[source,java,indent=0]
109+
----
110+
@Target({ ElementType.FIELD, ElementType.PARAMETER })
111+
@Retention(RetentionPolicy.RUNTIME)
112+
@ExtendWith(RandomNumberExtension.class)
113+
public @interface Random {
114+
}
115+
----
116+
117+
[source,java,indent=0]
118+
----
119+
class RandomNumberTests {
120+
121+
// use random number field in test methods and @BeforeEach
122+
// or @AfterEach lifecycle methods
123+
@Random
124+
private int randomNumber1;
125+
126+
RandomNumberTests(@Random int randomNumber2) {
127+
// use random number in constructor
128+
}
129+
130+
@BeforeEach
131+
void beforeEach(@Random int randomNumber3) {
132+
// use random number in @BeforeEach method
133+
}
134+
135+
@Test
136+
void test(@Random int randomNumber4) {
137+
// use random number in test method
138+
}
139+
}
140+
----
141+
142+
[TIP]
143+
.Extension Registration Order for `@ExtendWith` on Fields
144+
====
145+
Extensions registered declaratively via `@ExtendWith` on fields will be ordered relative
146+
to `@RegisterExtension` fields and other `@ExtendWith` fields using an algorithm that is
147+
deterministic but intentionally nonobvious. However, `@ExtendWith` fields can be ordered
148+
using the `@Order` annotation. See the <<extensions-registration-programmatic-order,
149+
Extension Registration Order>> tip for `@RegisterExtension` fields for details.
150+
====
151+
152+
NOTE: `@ExtendWith` fields may be either `static` or non-static. The documentation on
153+
<<extensions-registration-programmatic-static-fields, Static Fields>> and
154+
<<extensions-registration-programmatic-instance-fields, Instance Fields>> for
155+
`@RegisterExtension` fields also applies to `@ExtendWith` fields.
156+
95157
[[extensions-registration-programmatic]]
96158
==== Programmatic Extension Registration
97159

@@ -108,23 +170,23 @@ extension's constructor, a static factory method, or a builder API.
108170
[TIP]
109171
.Extension Registration Order
110172
====
111-
By default, extensions registered programmatically via `@RegisterExtension` will be
112-
ordered using an algorithm that is deterministic but intentionally nonobvious. This
113-
ensures that subsequent runs of a test suite execute extensions in the same order, thereby
114-
allowing for repeatable builds. However, there are times when extensions need to be
115-
registered in an explicit order. To achieve that, annotate `@RegisterExtension` fields
116-
with `{Order}`.
117-
118-
Any `@RegisterExtension` field not annotated with `@Order` will be ordered using the
119-
_default_ order which has a value of `Integer.MAX_VALUE / 2`. This allows `@Order`
120-
annotated extension fields to be explicitly ordered before or after non-annotated
121-
extension fields. Extensions with an explicit order value less than the default order
122-
value will be registered before non-annotated extensions. Similarly, extensions with an
123-
explicit order value greater than the default order value will be registered after
124-
non-annotated extensions. For example, assigning an extension an explicit order value that
125-
is greater than the default order value allows _before_ callback extensions to be
126-
registered last and _after_ callback extensions to be registered first, relative to other
127-
programmatically registered extensions.
173+
By default, extensions registered programmatically via `@RegisterExtension` or
174+
declaratively via `@ExtendWith` on fields will be ordered using an algorithm that is
175+
deterministic but intentionally nonobvious. This ensures that subsequent runs of a test
176+
suite execute extensions in the same order, thereby allowing for repeatable builds.
177+
However, there are times when extensions need to be registered in an explicit order. To
178+
achieve that, annotate `@RegisterExtension` fields or `@ExtendWith` fields with `{Order}`.
179+
180+
Any `@RegisterExtension` field or `@ExtendWith` field not annotated with `@Order` will be
181+
ordered using the _default_ order which has a value of `Integer.MAX_VALUE / 2`. This
182+
allows `@Order` annotated extension fields to be explicitly ordered before or after
183+
non-annotated extension fields. Extensions with an explicit order value less than the
184+
default order value will be registered before non-annotated extensions. Similarly,
185+
extensions with an explicit order value greater than the default order value will be
186+
registered after non-annotated extensions. For example, assigning an extension an explicit
187+
order value that is greater than the default order value allows _before_ callback
188+
extensions to be registered last and _after_ callback extensions to be registered first,
189+
relative to other programmatically registered extensions.
128190
====
129191

130192
NOTE: `@RegisterExtension` fields must not be `null` (at evaluation time) but may be
@@ -151,19 +213,18 @@ lifecycle methods annotated with `@BeforeAll` or `@AfterAll` as well as `@Before
151213
`server` field if necessary.
152214

153215
[source,java,indent=0]
154-
.An extension registered via a static field
216+
.Registering an extension via a static field in Java
155217
----
156218
include::{testDir}/example/registration/WebServerDemo.java[tags=user_guide]
157219
----
158220

159221
[[extensions-registration-programmatic-static-fields-kotlin]]
160-
===== Static Fields in Kotlin
222+
====== Static Fields in Kotlin
161223

162224
The Kotlin programming language does not have the concept of a `static` field. However,
163-
the compiler can be instructed to generate static fields using annotations. Since, as
164-
stated earlier, `@RegisterExtension` fields must not be `null`, one **cannot** use the
165-
`@JvmStatic` annotation in Kotlin as it generates `private` fields. Rather, the
166-
`@JvmField` annotation must be used.
225+
the compiler can be instructed to generate a `private static` field using the `@JvmStatic`
226+
annotation in Kotlin. If you want the Kotlin compiler to generate a `public static` field,
227+
you can use the `@JvmField` annotation instead.
167228

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

documentation/src/test/kotlin/example/registration/KotlinWebServerDemo.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import org.junit.jupiter.api.extension.RegisterExtension
1717
class KotlinWebServerDemo {
1818

1919
companion object {
20-
@JvmField
20+
@JvmStatic
2121
@RegisterExtension
2222
val server = WebServerExtension.builder()
2323
.enableSecurity(false)

junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ExtendWith.java

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,46 @@
2525
/**
2626
* {@code @ExtendWith} is a {@linkplain Repeatable repeatable} annotation
2727
* that is used to register {@linkplain Extension extensions} for the annotated
28-
* test class, test interface, test method, field, or parameter.
28+
* test class, test interface, test method, parameter, or field.
2929
*
3030
* <p>Annotated parameters are supported in test class constructors, in test
3131
* methods, and in {@code @BeforeAll}, {@code @AfterAll}, {@code @BeforeEach},
3232
* and {@code @AfterEach} lifecycle methods.
3333
*
34+
* <p>{@code @ExtendWith} fields may be either {@code static} or non-static.
35+
*
36+
* <h3>Inheritance</h3>
37+
*
38+
* <p>{@code @ExtendWith} fields are inherited from superclasses as long as they
39+
* are not <em>hidden</em> or <em>overridden</em>. Furthermore, {@code @ExtendWith}
40+
* fields from superclasses will be registered before {@code @ExtendWith} fields
41+
* in subclasses.
42+
*
43+
* <h3>Registration Order</h3>
44+
*
45+
* <p>When {@code @ExtendWith} is present on a test class, test interface, or
46+
* test method or on a parameter in a test method or lifecycle method, the
47+
* corresponding extensions will be registered in the order in which the
48+
* {@code @ExtendWith} annotations are discovered. For example, if a test class
49+
* is annotated with {@code @ExtendWith(A.class)} and then with
50+
* {@code @ExtendWith(B.class)}, extension {@code A} will be registered before
51+
* extension {@code B}.
52+
*
53+
* <p>By default, if multiple extensions are registered on fields via
54+
* {@code @ExtendWith}, they will be ordered using an algorithm that is
55+
* deterministic but intentionally nonobvious. This ensures that subsequent runs
56+
* of a test suite execute extensions in the same order, thereby allowing for
57+
* repeatable builds. However, there are times when extensions need to be
58+
* registered in an explicit order. To achieve that, you can annotate
59+
* {@code @ExtendWith} fields with {@link org.junit.jupiter.api.Order @Order}.
60+
* Any {@code @ExtendWith} field not annotated with {@code @Order} will be
61+
* ordered using the {@link org.junit.jupiter.api.Order#DEFAULT default} order
62+
* value. Note that {@code @RegisterExtension} fields can also be ordered with
63+
* {@code @Order}, relative to {@code @ExtendWith} fields and other
64+
* {@code @RegisterExtension} fields.
65+
*
3466
* <h3>Supported Extension APIs</h3>
67+
*
3568
* <ul>
3669
* <li>{@link ExecutionCondition}</li>
3770
* <li>{@link InvocationInterceptor}</li>

junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/RegisterExtension.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,9 @@
8181
* {@code @RegisterExtension} fields with {@link org.junit.jupiter.api.Order @Order}.
8282
* Any {@code @RegisterExtension} field not annotated with {@code @Order} will be
8383
* ordered using the {@link org.junit.jupiter.api.Order#DEFAULT default} order
84-
* value.
84+
* value. Note that {@code @ExtendWith} fields can also be ordered with
85+
* {@code @Order}, relative to {@code @RegisterExtension} fields and other
86+
* {@code @ExtendWith} fields.
8587
*
8688
* <h3>Example Usage</h3>
8789
*

junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ExtensionUtils.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -76,8 +76,8 @@ static MutableExtensionRegistry populateNewExtensionRegistryFromExtendWithAnnota
7676

7777
/**
7878
* Register extensions using the supplied registrar from fields in the supplied
79-
* class that are meta-annotated with {@link ExtendWith @ExtendWith} or
80-
* annotated with {@link RegisterExtension @RegisterExtension}.
79+
* class that are annotated with {@link ExtendWith @ExtendWith} or
80+
* {@link RegisterExtension @RegisterExtension}.
8181
*
8282
* <p>The extensions will be sorted according to {@link Order @Order} semantics
8383
* prior to registration.
@@ -141,7 +141,7 @@ static void registerExtensionsFromConstructorParameters(ExtensionRegistrar regis
141141
/**
142142
* Register extensions using the supplied registrar from parameters in the
143143
* supplied {@link Executable} (i.e., a {@link java.lang.reflect.Constructor}
144-
* or {@link java.lang.reflect.Method}) that are meta-annotated with
144+
* or {@link java.lang.reflect.Method}) that are annotated with
145145
* {@link ExtendWith @ExtendWith}.
146146
*
147147
* @param registrar the registrar with which to register the extensions; never {@code null}

0 commit comments

Comments
 (0)