Skip to content

Revert to a simplified extension model #237

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 13 additions & 90 deletions documentation/src/docs/asciidoc/extensions.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,7 @@ JUnit 4, the JUnit 5 extension model consists of a single, coherent concept: the
[[extension-registration]]
=== Registering Extensions

Extensions can be registered either declaratively via `{ExtendWith}` or programmatically
via an `{ExtensionRegistrar}`.
Extensions can be registered declaratively via `{ExtendWith}`.

Note that registered extensions are inherited within test class hierarchies.

Expand Down Expand Up @@ -60,85 +59,6 @@ class MyTestsV2 {
The execution of tests in both `MyTestsV1` and `MyTestsV2` will be extended by the
`FooExtension` and `BarExtension`, in exactly that order.

[[extension-registration-programmatic]]
==== Programmatic Extension Registration

Developers can register one or more extensions _programmatically_ by implementing the
`{ExtensionRegistrar}` API and registering the custom `ExtensionRegistrar` implementation
via `@ExtendWith` as mentioned above. The following examples demonstrate the various
options in implementations of the `ExtensionRegistrar.registerExtensions()` method.

===== Registering an ExtensionPoint Instance

If you have an instance of an extension that implements one or more `ExtensionPoint`
APIs, you can register it as follows using the _default_ `Position`.

[source,java,indent=0]
[subs="verbatim"]
----
public void registerExtensions(ExtensionPointRegistry registry) {
CustomExtension customExtension = // instantiate extension
registry.register(customExtension);
}
----

If you would like to influence the order in which multiple implementations of the same
`ExtensionPoint` API are applied, you can do so by registering the extensions
programmatically and supplying the `Position`. The following values are permitted.

- `OUTERMOST`: Applied first. Only a single extension is allowed to be assigned this
position; otherwise, an `ExtensionConfigurationException` will be thrown.
- `OUTSIDE_DEFAULT`: Applied after `OUTERMOST` but before `DEFAULT`, `INSIDE_DEFAULT`,
and `INNERMOST`. Multiple extensions can be assigned this position; however, the
ordering among such extensions is undefined.
- `DEFAULT`: (?)
- `INSIDE_DEFAULT`: (?)
- `INNERMOST`: (?)

The following listing demonstrates how to configure an explicit position for an instance
of an extension. Note, however, that a `Position` can also be supplied for lambda
expressions and method references.

[source,java,indent=0]
[subs="verbatim"]
----
public void registerExtensions(ExtensionPointRegistry registry) {
registry.register(new TimingExtension(), Position.INNERMOST);
}
----

===== Registering a Lambda Expression as an ExtensionPoint

If you would like to implement a single `ExtensionPoint` API as a _lambda expression_,
you can register it as follows. Note, however, that the API must be a _functional
interface_.

[source,java,indent=0]
[subs="verbatim"]
----
public void registerExtensions(ExtensionPointRegistry registry) {
registry.register((BeforeEachExtensionPoint) context -> { /* ... */ });
}
----

===== Registering a Method Reference as an ExtensionPoint

If you would like to implement a single `ExtensionPoint` API via a _method reference_,
you can register it as follows. Note, however, that the API must be a _functional
interface_.

[source,java,indent=0]
[subs="verbatim"]
----
public void registerExtensions(ExtensionPointRegistry registry) {
registry.register((BeforeEachExtensionPoint) this::beforeEach);
}

void beforeEach(TestExtensionContext context) {
/* ... */
}
----


=== Conditional Test Execution

Expand All @@ -165,6 +85,7 @@ initialization methods on the test instance, etc.
For concrete examples, consult the source code for the `{MockitoExtension}` and the
`{SpringExtension}`.


=== Parameter Resolution

`{MethodParameterResolver}` is an `Extension` strategy for dynamically resolving
Expand All @@ -178,25 +99,29 @@ resolved by _name_, _type_, _annotation_, or any combination thereof. For concre
consult the source code for `{CustomTypeParameterResolver}` and
`{CustomAnnotationParameterResolver}`.


=== Test Lifecycle Callbacks

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

* `BeforeEachExtensionPoint`
* `AfterEachExtensionPoint`
* `BeforeAllExtensionPoint`
* `AfterAllExtensionPoint`
* `BeforeAllCallback`
* `AfterAllCallback`
* `BeforeEachCallback`
* `AfterEachCallback`
* `BeforeTestMethodCallback`
* `AfterTestMethodCallback`

Note that 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.


=== Exception Handling

`{ExceptionHandlerExtensionPoint}` defines the API for `Extensions` that wish to _react to
exceptions_ thrown in a test.
`{ExceptionHandler}` defines the API for `Extensions` that wish to handle exceptions
thrown from tests.

The following example shows an extension which will swallow all instances of `IOException`
but rethrow any other type of exception:
Expand All @@ -209,14 +134,12 @@ include::{testDir}/example/exception/IgnoreIOExceptionExtension.java[tags=user_g
----



=== 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 point to the next? ...



=== Additional Planned Extensions

Several additional extensions are planned, including but not limited to the following:
Expand Down
2 changes: 1 addition & 1 deletion documentation/src/docs/asciidoc/index.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ Stefan Bechtold; Sam Brannen; Johannes Link; Matthias Merdes; Marc Philipp
:CustomTypeParameterResolver: {master-branch}/junit-tests/src/test/java/org/junit/gen5/engine/junit5/execution/injection/sample/CustomTypeParameterResolver.java[CustomTypeParameterResolver]
:Disabled: {master-branch}/junit5-api/src/main/java/org/junit/gen5/api/Disabled.java[@Disabled]
:DisabledCondition: {master-branch}/junit5-engine/src/main/java/org/junit/gen5/engine/junit5/extension/DisabledCondition.java[DisabledCondition]
:ExceptionHandlerExtensionPoint: {master-branch}/junit5-api/src/main/java/org/junit/gen5/api/extension/ExceptionHandlerExtensionPoint.java[ExceptionHandlerExtensionPoint]
:ExceptionHandler: {master-branch}/junit5-api/src/main/java/org/junit/gen5/api/extension/ExceptionHandler.java[ExceptionHandler]
:ExtendWith: {master-branch}/junit5-api/src/main/java/org/junit/gen5/api/extension/ExtendWith.java[@ExtendWith]
:ExtensionRegistrar: {master-branch}/junit5-api/src/main/java/org/junit/gen5/api/extension/ExtensionRegistrar.java[ExtensionRegistrar]
:InstancePostProcessor: {master-branch}/junit5-api/src/main/java/org/junit/gen5/api/extension/InstancePostProcessor.java[InstancePostProcessor]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@

import java.io.IOException;

import org.junit.gen5.api.extension.ExceptionHandlerExtensionPoint;
import org.junit.gen5.api.extension.ExceptionHandler;
import org.junit.gen5.api.extension.TestExtensionContext;

// @formatter:off
// tag::user_guide[]
public class IgnoreIOExceptionExtension implements ExceptionHandlerExtensionPoint {
public class IgnoreIOExceptionExtension implements ExceptionHandler {

@Override
public void handleException(TestExtensionContext context, Throwable throwable)
Expand Down
45 changes: 15 additions & 30 deletions documentation/src/test/java/example/timing/TimingExtension.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,13 @@

package example.timing;

import static org.junit.gen5.api.extension.ExtensionPointRegistry.Position.INNERMOST;

import java.lang.reflect.Method;
import java.util.logging.Logger;

import org.junit.gen5.api.extension.AfterEachExtensionPoint;
import org.junit.gen5.api.extension.BeforeEachExtensionPoint;
import org.junit.gen5.api.extension.ExtensionContext;
import org.junit.gen5.api.extension.AfterTestMethodCallback;
import org.junit.gen5.api.extension.BeforeTestMethodCallback;
import org.junit.gen5.api.extension.ExtensionContext.Namespace;
import org.junit.gen5.api.extension.ExtensionPointRegistry;
import org.junit.gen5.api.extension.ExtensionRegistrar;
import org.junit.gen5.api.extension.ExtensionContext.Store;
import org.junit.gen5.api.extension.TestExtensionContext;

/**
Expand All @@ -29,37 +25,26 @@
*
* @since 5.0
*/
public class TimingExtension implements ExtensionRegistrar {
public class TimingExtension implements BeforeTestMethodCallback, AfterTestMethodCallback {

private static final Logger LOG = Logger.getLogger(TimingExtension.class.getName());

@Override
public void registerExtensions(ExtensionPointRegistry registry) {
registry.register(new TestMethodInvocationWrapper(), INNERMOST);
public void beforeTestMethod(TestExtensionContext context) throws Exception {
getStore(context).put(context.getTestMethod(), System.currentTimeMillis());
}

private static class TestMethodInvocationWrapper implements BeforeEachExtensionPoint, AfterEachExtensionPoint {

@Override
public void beforeEach(TestExtensionContext context) throws Exception {
ExtensionContext.Store times = context.getStore(getNamespace(context));
times.put(context.getTestMethod(), System.currentTimeMillis());
}

private Namespace getNamespace(TestExtensionContext context) {
return Namespace.of(getClass(), context);
}

@Override
public void afterEach(TestExtensionContext context) throws Exception {
ExtensionContext.Store times = context.getStore(getNamespace(context));
Method testMethod = context.getTestMethod();
long start = (long) times.remove(testMethod);
long duration = System.currentTimeMillis() - start;
@Override
public void afterTestMethod(TestExtensionContext context) throws Exception {
Method testMethod = context.getTestMethod();
long start = (long) getStore(context).remove(testMethod);
long duration = System.currentTimeMillis() - start;

LOG.info(() -> String.format("Method [%s] took %s ms.", testMethod, duration));
}
LOG.info(() -> String.format("Method [%s] took %s ms.", testMethod, duration));
}

private Store getStore(TestExtensionContext context) {
return context.getStore(Namespace.of(getClass(), context));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,13 @@

import static org.junit.gen5.api.Assertions.assertNotNull;

import org.junit.gen5.api.extension.AfterEachExtensionPoint;
import org.junit.gen5.api.extension.ExceptionHandlerExtensionPoint;
import org.junit.gen5.api.extension.AfterEachCallback;
import org.junit.gen5.api.extension.ExceptionHandler;
import org.junit.gen5.api.extension.ExtensionContext.Namespace;
import org.junit.gen5.api.extension.ExtensionContext.Store;
import org.junit.gen5.api.extension.TestExtensionContext;

public class ExpectToFailExtension implements ExceptionHandlerExtensionPoint, AfterEachExtensionPoint {
public class ExpectToFailExtension implements ExceptionHandler, AfterEachCallback {

@Override
public void handleException(TestExtensionContext context, Throwable throwable) throws Throwable {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,15 @@
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;

import org.junit.gen5.api.extension.AfterEachExtensionPoint;
import org.junit.gen5.api.extension.AfterEachCallback;
import org.junit.gen5.api.extension.ExtensionContext;
import org.junit.gen5.api.extension.ExtensionContext.Namespace;
import org.junit.gen5.api.extension.MethodInvocationContext;
import org.junit.gen5.api.extension.MethodParameterResolver;
import org.junit.gen5.api.extension.ParameterResolutionException;
import org.junit.gen5.api.extension.TestExtensionContext;

public class TempDirectory implements AfterEachExtensionPoint, MethodParameterResolver {
public class TempDirectory implements AfterEachCallback, MethodParameterResolver {

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
Expand Down
Loading