Skip to content

Commit

Permalink
Create specialized sub-interface of Extension
Browse files Browse the repository at this point in the history
This enforces that only potentially affected extensions can override the
method determining the extension context scope during test class
instance construction.
  • Loading branch information
marcphilipp committed Oct 9, 2024
1 parent 6b80f42 commit ba6cd95
Show file tree
Hide file tree
Showing 16 changed files with 185 additions and 155 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,10 @@ JUnit repository on GitHub.
extensions.
* Allow determining "shared resources" at runtime via the new `@ResourceLock#providers`
attribute that accepts implementations of `ResourceLocksProvider`.
* Overriding `Extension.getExtensionContextScopeDuringTestInstanceConstruction()` enables
using a test-scoped `ExtensionContext` in `Extension` methods called during test class
construction. This behavior will become the default in future versions of JUnit.
* Overriding `Extension.getExtensionContextScopeDuringTestClassInstanceConstruction()`
enables using a test-scoped `ExtensionContext` in `Extension` methods called during test
class instance construction. This behavior will become the default in future versions of
JUnit.
* `@TempDir` is now supported on test class constructors.


Expand Down
30 changes: 15 additions & 15 deletions documentation/src/docs/asciidoc/user-guide/extensions.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -384,9 +384,9 @@ instances and their lifecycle.
[NOTE]
====
You may override the
`{Extension}.getExtensionContextScopeDuringTestInstanceConstruction(...)` method to return
`TEST_SCOPED` for revised handling of `CloseableResource` and to make test-specific data
available to your implementation.
`{Extension}.getExtensionContextScopeDuringTestClassInstanceConstruction(...)` method to
return `TEST_SCOPED` for revised handling of `CloseableResource` and to make test-specific
data available to your implementation.
====

[[extensions-test-instance-factories]]
Expand Down Expand Up @@ -418,9 +418,9 @@ registered for any specific test class.
[NOTE]
====
You may override the
`{Extension}.getExtensionContextScopeDuringTestInstanceConstruction(...)` method to return
`TEST_SCOPED` for revised handling of `CloseableResource` and to make test-specific data
available to your implementation.
`{Extension}.getExtensionContextScopeDuringTestClassInstanceConstruction(...)` method to
return `TEST_SCOPED` for revised handling of `CloseableResource` and to make test-specific
data available to your implementation.
====

[[extensions-test-instance-post-processing]]
Expand All @@ -438,9 +438,9 @@ For a concrete example, consult the source code for the `{MockitoExtension}` and
[NOTE]
====
You may override the
`{Extension}.getExtensionContextScopeDuringTestInstanceConstruction(...)` method to return
`TEST_SCOPED` for revised handling of `CloseableResource` and to make test-specific data
available to your implementation.
`{Extension}.getExtensionContextScopeDuringTestClassInstanceConstruction(...)` method to
return `TEST_SCOPED` for revised handling of `CloseableResource` and to make test-specific
data available to your implementation.
====

[[extensions-test-instance-pre-destroy-callback]]
Expand Down Expand Up @@ -492,10 +492,10 @@ those provided in `java.lang.reflect.Parameter` in order to avoid this bug in th
[NOTE]
====
You may override the
`{Extension}.getExtensionContextScopeDuringTestInstanceConstruction(...)` method to return
`TEST_SCOPED` to support injecting test specific data into constructor parameters of the
test instance. Doing so causes a test-specific `{ExtensionContext}` to be used while
resolving constructor parameters, unless the
`{Extension}.getExtensionContextScopeDuringTestClassInstanceConstruction(...)` method to
return `TEST_SCOPED` to support injecting test specific data into constructor parameters
of the test class instance. Doing so causes a test-specific `{ExtensionContext}` to be
used while resolving constructor parameters, unless the
<<writing-tests-test-instance-lifecycle, test instance lifecycle>> is set to `PER_CLASS`.
====

Expand Down Expand Up @@ -732,8 +732,8 @@ include::{testDir}/example/interceptor/SwingEdtInterceptor.java[tags=user_guide]
[NOTE]
====
You may override the
`{Extension}.getExtensionContextScopeDuringTestInstanceConstruction(...)` method to return
`TEST_SCOPED` to make test-specific data available to your implementation of
`{Extension}.getExtensionContextScopeDuringTestClassInstanceConstruction(...)` method to
return `TEST_SCOPED` to make test-specific data available to your implementation of
`interceptTestClassConstructor` and for a revised scope of the provided `Store` instance.
====

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,9 @@

package org.junit.jupiter.api.extension;

import static org.apiguardian.api.API.Status.DEPRECATED;
import static org.apiguardian.api.API.Status.EXPERIMENTAL;
import static org.apiguardian.api.API.Status.STABLE;

import org.apiguardian.api.API;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.extension.ExtensionContext.Store;
import org.junit.jupiter.api.extension.ExtensionContext.Store.CloseableResource;

/**
* Marker interface for all extensions.
Expand All @@ -43,97 +38,4 @@
*/
@API(status = STABLE, since = "5.0")
public interface Extension {

/**
* Whether this extension should receive a test-scoped
* {@link ExtensionContext} during the creation of test instances.
*
* <p>If an extension returns
* {@link ExtensionContextScope#TEST_SCOPED TEST_SCOPED} from this method,
* the following extension methods will be called with a test-scoped
* {@link ExtensionContext} instead of a class-scoped one, unless the
* {@link TestInstance.Lifecycle#PER_CLASS PER_CLASS} lifecycle is used:
*
* <ul>
* <li>{@link InvocationInterceptor#interceptTestClassConstructor}</li>
* <li>{@link ParameterResolver} when resolving constructor parameters</li>
* <li>{@link TestInstancePreConstructCallback}</li>
* <li>{@link TestInstancePostProcessor}</li>
* <li>{@link TestInstanceFactory}</li>
* </ul>
*
* <p>In such cases, implementations of these extension callbacks can
* observe the following differences:
*
* <ul>
* <li>{@link ExtensionContext#getElement() getElement()} may refer to the
* test method and {@link ExtensionContext#getTestClass() getTestClass()}
* may refer to a nested test class.
* Use {@link TestInstanceFactoryContext#getTestClass()} to get the class
* under construction.</li>
* <li>{@link ExtensionContext#getTestMethod() getTestMethod()} is no-longer
* empty, unless the test class is annotated with
* {@link TestInstance @TestInstance(Lifecycle.PER_CLASS)}.</li>
* <li>If the callback adds a new {@link CloseableResource} to the
* {@link Store}, the resource is closed just after the instance is
* destroyed.</li>
* <li>The callbacks can now access data previously stored by
* {@link TestTemplateInvocationContext}, unless the test class is annotated
* with {@link TestInstance @TestInstance(Lifecycle.PER_CLASS)}.</li>
* </ul>
*
* <p><strong>Note</strong>: The behavior which is enabled by returning
* {@link ExtensionContextScope#TEST_SCOPED TEST_SCOPED} from this method
* will become the default in future versions of JUnit. To ensure future
* compatibility, extension implementors are therefore advised to opt in,
* even if they don't require the new functionality.
*
* @implNote There are no guarantees about how often this method is called.
* Therefore, implementations should be idempotent and avoid side
* effects. They may, however, cache the result for performance in
* the {@link Store Store} of the supplied
* {@link ExtensionContext}, if necessary.
* @param rootContext the root extension context to allow inspection of
* configuration parameters; never {@code null}
* @since 5.12
* @see InvocationInterceptor#interceptTestClassConstructor
* @see ParameterResolver
* @see TestInstancePreConstructCallback
* @see TestInstancePostProcessor
* @see TestInstanceFactory
*/
@API(status = EXPERIMENTAL, since = "5.12")
default ExtensionContextScope getExtensionContextScopeDuringTestInstanceConstruction(ExtensionContext rootContext) {
return ExtensionContextScope.DEFAULT;
}

/**
* {@code ExtensionContextScope} is used to define the scope of the
* {@link ExtensionContext} passed to an extension during the creation of
* test instances.
*
* @since 5.12
* @see org.junit.jupiter.api.extension.Extension#getExtensionContextScopeDuringTestInstanceConstruction
*/
@API(status = EXPERIMENTAL, since = "5.12")
enum ExtensionContextScope {

/**
* The extension should receive an {@link ExtensionContext} scoped to
* the test class.
*
* @deprecated This behavior will be removed from future versions of
* JUnit and {@link #TEST_SCOPED} will become the default.
*/
@API(status = DEPRECATED, since = "5.12") //
@Deprecated
DEFAULT,

/**
* The extension should receive an {@link ExtensionContext} scoped to
* the test instance.
*/
TEST_SCOPED
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
* @see ExtensionContext
*/
@API(status = STABLE, since = "5.10")
public interface InvocationInterceptor extends Extension {
public interface InvocationInterceptor extends TestClassInstanceConstructionParticipatingExtension {

/**
* Intercept the invocation of a test class constructor.
Expand All @@ -60,9 +60,9 @@ public interface InvocationInterceptor extends Extension {
* (static initialization) when this method is invoked.
*
* <p>Extensions may override
* {@link #getExtensionContextScopeDuringTestInstanceConstruction} to make
* test-specific data available to the implementation of this method and for
* a revised scope of the provided {@link Store} instance.
* {@link #getExtensionContextScopeDuringTestClassInstanceConstruction} to
* make test-specific data available to the implementation of this method
* and for a revised scope of the provided {@link Store Store} instance.
*
* @param invocation the invocation that is being intercepted; never
* {@code null}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,12 @@
* {@code ParameterResolver}.
*
* <p>Extensions may override
* {@link #getExtensionContextScopeDuringTestInstanceConstruction} to support
* injecting test specific data into constructor parameters of the test
* instance. Returning {@link ExtensionContextScope#TEST_SCOPED TEST_SCOPED}
* from this method, causes a test-specific {@link ExtensionContext} to be used
* while resolving constructor parameters, unless the lifecycle is set to
* {@link #getExtensionContextScopeDuringTestClassInstanceConstruction} to
* support injecting test specific data into constructor parameters of the test
* class instance. Returning
* {@link ExtensionContextScope#TEST_SCOPED TEST_SCOPED} from this method,
* causes a test-specific {@link ExtensionContext} to be used while resolving
* constructor parameters, unless the lifecycle is set to
* {@link TestInstance.Lifecycle#PER_CLASS PER_CLASS}.
*
* <h2>Constructor Requirements</h2>
Expand All @@ -53,7 +54,7 @@
* @see TestInstancePreDestroyCallback
*/
@API(status = STABLE, since = "5.0")
public interface ParameterResolver extends Extension {
public interface ParameterResolver extends TestClassInstanceConstructionParticipatingExtension {

/**
* Determine if this resolver supports resolution of an argument for the
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
/*
* Copyright 2015-2024 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/

package org.junit.jupiter.api.extension;

import static org.apiguardian.api.API.Status.DEPRECATED;
import static org.apiguardian.api.API.Status.EXPERIMENTAL;

import org.apiguardian.api.API;
import org.junit.jupiter.api.TestInstance;

/**
* Interface for {@link Extension Extensions} that participate in the
* construction of test class instances.
*
* @since 5.12
* @see InvocationInterceptor#interceptTestClassConstructor
* @see ParameterResolver
* @see TestInstancePreConstructCallback
* @see TestInstancePostProcessor
* @see TestInstanceFactory
*/
@API(status = EXPERIMENTAL, since = "5.12")
public interface TestClassInstanceConstructionParticipatingExtension extends Extension {

/**
* Whether this extension should receive a test-scoped
* {@link ExtensionContext} during the creation of test instances.
*
* <p>If an extension returns
* {@link ExtensionContextScope#TEST_SCOPED TEST_SCOPED} from this method,
* the following extension methods will be called with a test-scoped
* {@link ExtensionContext} instead of a class-scoped one, unless the
* {@link TestInstance.Lifecycle#PER_CLASS PER_CLASS} lifecycle is used:
*
* <ul>
* <li>{@link InvocationInterceptor#interceptTestClassConstructor}</li>
* <li>{@link ParameterResolver} when resolving constructor parameters</li>
* <li>{@link TestInstancePreConstructCallback}</li>
* <li>{@link TestInstancePostProcessor}</li>
* <li>{@link TestInstanceFactory}</li>
* </ul>
*
* <p>In such cases, implementations of these extension callbacks can
* observe the following differences:
*
* <ul>
* <li>{@link ExtensionContext#getElement() getElement()} may refer to the
* test method and {@link ExtensionContext#getTestClass() getTestClass()}
* may refer to a nested test class.
* Use {@link TestInstanceFactoryContext#getTestClass()} to get the class
* under construction.</li>
* <li>{@link ExtensionContext#getTestMethod() getTestMethod()} is no-longer
* empty, unless the test class is annotated with
* {@link TestInstance @TestInstance(Lifecycle.PER_CLASS)}.</li>
* <li>If the callback adds a new {@link ExtensionContext.Store.CloseableResource} to the
* {@link ExtensionContext.Store}, the resource is closed just after the instance is
* destroyed.</li>
* <li>The callbacks can now access data previously stored by
* {@link TestTemplateInvocationContext}, unless the test class is annotated
* with {@link TestInstance @TestInstance(Lifecycle.PER_CLASS)}.</li>
* </ul>
*
* <p><strong>Note</strong>: The behavior which is enabled by returning
* {@link ExtensionContextScope#TEST_SCOPED TEST_SCOPED} from this method
* will become the default in future versions of JUnit. To ensure future
* compatibility, extension implementors are therefore advised to opt in,
* even if they don't require the new functionality.
*
* @implNote There are no guarantees about how often this method is called.
* Therefore, implementations should be idempotent and avoid side
* effects. They may, however, cache the result for performance in
* the {@link ExtensionContext.Store Store} of the supplied
* {@link ExtensionContext}, if necessary.
* @param rootContext the root extension context to allow inspection of
* configuration parameters; never {@code null}
* @since 5.12
*/
@API(status = EXPERIMENTAL, since = "5.12")
default ExtensionContextScope getExtensionContextScopeDuringTestClassInstanceConstruction(
ExtensionContext rootContext) {
return ExtensionContextScope.DEFAULT;
}

/**
* {@code ExtensionContextScope} is used to define the scope of the
* {@link ExtensionContext} passed to an extension during the creation of
* test instances.
*
* @since 5.12
* @see TestClassInstanceConstructionParticipatingExtension#getExtensionContextScopeDuringTestClassInstanceConstruction
*/
@API(status = EXPERIMENTAL, since = "5.12")
enum ExtensionContextScope {

/**
* The extension should receive an {@link ExtensionContext} scoped to
* the test class.
*
* @deprecated This behavior will be removed from future versions of
* JUnit and {@link #TEST_SCOPED} will become the default.
*/
@API(status = DEPRECATED, since = "5.12") //
@Deprecated
DEFAULT,

/**
* The extension should receive an {@link ExtensionContext} scoped to
* the test instance.
*/
TEST_SCOPED
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -52,13 +52,13 @@
*/
@FunctionalInterface
@API(status = STABLE, since = "5.7")
public interface TestInstanceFactory extends Extension {
public interface TestInstanceFactory extends TestClassInstanceConstructionParticipatingExtension {

/**
* Callback for creating a test instance for the supplied context.
*
* <p>Extensions may override
* {@link #getExtensionContextScopeDuringTestInstanceConstruction} for
* {@link #getExtensionContextScopeDuringTestClassInstanceConstruction} for
* revised handling of {@link CloseableResource CloseableResource} and to
* make test-specific data available to your implementation.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,13 @@
*/
@FunctionalInterface
@API(status = STABLE, since = "5.0")
public interface TestInstancePostProcessor extends Extension {
public interface TestInstancePostProcessor extends TestClassInstanceConstructionParticipatingExtension {

/**
* Callback for post-processing the supplied test instance.
*
* <p>Extensions may override
* {@link #getExtensionContextScopeDuringTestInstanceConstruction} for
* {@link #getExtensionContextScopeDuringTestClassInstanceConstruction} for
* revised handling of {@link CloseableResource CloseableResource} and to
* make test-specific data available to your implementation.
*
Expand Down
Loading

0 comments on commit ba6cd95

Please sign in to comment.