Repository: junit-team/junit5 (language: java)

Issue:
Methods/constructors invoked via ExecutableInvoker should have access to current ExtensionContext and ExtensionRegistry
### Discussed in https://github.com/junit-team/junit5/discussions/3896

<div type='discussions-op-text'>

<sup>Originally posted by **alanwong-toast** July 22, 2024</sup>
I was hoping that [ExecutableInvoker](https://junit.org/junit5/docs/current/api/org.junit.jupiter.api/org/junit/jupiter/api/extension/ExecutableInvoker.html) (accessible from `ExtensionContext`) would allow for the invocation of methods/classes with the currently known set of `ParameterResolvers`, but based on my testing, it seems that it only loads with a default set of resolvers and never the resolvers I have injected.

The creation of the `DefaultExecutableInvoker` happens here:
https://github.com/junit-team/junit5/blob/ae20d63bd301171200a2b0f7b71d5ebd915e0eff/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ClassBasedTestDescriptor.java#L149-L151
https://github.com/junit-team/junit5/blob/ae20d63bd301171200a2b0f7b71d5ebd915e0eff/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ClassBasedTestDescriptor.java#L179-L192

With this constructor for `DefaultExecutableInvoker`: https://github.com/junit-team/junit5/blob/ae20d63bd301171200a2b0f7b71d5ebd915e0eff/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/DefaultExecutableInvoker.java#L40-L42

It appears that the new extension registry generated from `populateNewExtensionRegistryFromExtendWithAnnotation` at the top is never passed into the new context that is used to construct the class's context.

This results in a default set of resolvers in all cases, regardless of which extension interface I use to get the extension context.

Is there a correct way to utilize `ExecutableInvoker` such that the context of the currently running test has access to that test's `ParameterResolvers`?</div>


Discussion / hints:

Fix patch:
diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-5.11.0-RC1.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-5.11.0-RC1.adoc
index 8ede33a6efa0..5eff609cd824 100644
--- a/documentation/src/docs/asciidoc/release-notes/release-notes-5.11.0-RC1.adoc
+++ b/documentation/src/docs/asciidoc/release-notes/release-notes-5.11.0-RC1.adoc
@@ -44,6 +44,9 @@ repository on GitHub.
 
 * `TestInstancePostProcessor` extensions can now be registered via the `@ExtendWith`
   annotation on non-static fields.
+* Methods and constructors invoked via `ExecutableInvoker` now use the same extensions as
+  the context they are invoked from and the same `ExtensionContext` is passed to
+  registered instances of `ParameterResolver`.
 
 [[release-notes-5.11.0-RC1-junit-jupiter-deprecations-and-breaking-changes]]
 ==== Deprecations and Breaking Changes
diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/AbstractExtensionContext.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/AbstractExtensionContext.java
index 28f76b92e30b..a1772e796146 100644
--- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/AbstractExtensionContext.java
+++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/AbstractExtensionContext.java
@@ -55,8 +55,9 @@ abstract class AbstractExtensionContext<T extends TestDescriptor> implements Ext
 	private final ExecutableInvoker executableInvoker;
 
 	AbstractExtensionContext(ExtensionContext parent, EngineExecutionListener engineExecutionListener, T testDescriptor,
-			JupiterConfiguration configuration, ExecutableInvoker executableInvoker) {
-		this.executableInvoker = executableInvoker;
+			JupiterConfiguration configuration,
+			Function<ExtensionContext, ExecutableInvoker> executableInvokerFactory) {
+		this.executableInvoker = executableInvokerFactory.apply(this);
 
 		Preconditions.notNull(testDescriptor, "TestDescriptor must not be null");
 		Preconditions.notNull(configuration, "JupiterConfiguration must not be null");
diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ClassBasedTestDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ClassBasedTestDescriptor.java
index 4f52ac305f7c..baa884c18e7e 100644
--- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ClassBasedTestDescriptor.java
+++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ClassBasedTestDescriptor.java
@@ -39,7 +39,6 @@
 import org.junit.jupiter.api.TestInstance.Lifecycle;
 import org.junit.jupiter.api.extension.AfterAllCallback;
 import org.junit.jupiter.api.extension.BeforeAllCallback;
-import org.junit.jupiter.api.extension.ExecutableInvoker;
 import org.junit.jupiter.api.extension.Extension;
 import org.junit.jupiter.api.extension.ExtensionConfigurationException;
 import org.junit.jupiter.api.extension.ExtensionContext;
@@ -179,10 +178,9 @@ public JupiterEngineExecutionContext prepare(JupiterEngineExecutionContext conte
 		registerExtensionsFromInstanceFields(registry, this.testClass);
 
 		ThrowableCollector throwableCollector = createThrowableCollector();
-		ExecutableInvoker executableInvoker = new DefaultExecutableInvoker(context);
 		ClassExtensionContext extensionContext = new ClassExtensionContext(context.getExtensionContext(),
 			context.getExecutionListener(), this, this.lifecycle, context.getConfiguration(), throwableCollector,
-			executableInvoker);
+			it -> new DefaultExecutableInvoker(it, registry));
 
 		// @formatter:off
 		return context.extend()
diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ClassExtensionContext.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ClassExtensionContext.java
index 09156394183f..ddeb750dbb29 100644
--- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ClassExtensionContext.java
+++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ClassExtensionContext.java
@@ -13,6 +13,7 @@
 import java.lang.reflect.AnnotatedElement;
 import java.lang.reflect.Method;
 import java.util.Optional;
+import java.util.function.Function;
 
 import org.junit.jupiter.api.TestInstance.Lifecycle;
 import org.junit.jupiter.api.extension.ExecutableInvoker;
@@ -38,21 +39,23 @@ final class ClassExtensionContext extends AbstractExtensionContext<ClassBasedTes
 	 * Create a new {@code ClassExtensionContext} with {@link Lifecycle#PER_METHOD}.
 	 *
 	 * @see #ClassExtensionContext(ExtensionContext, EngineExecutionListener, ClassBasedTestDescriptor,
-	 * Lifecycle, JupiterConfiguration, ThrowableCollector, ExecutableInvoker)
+	 * Lifecycle, JupiterConfiguration, ThrowableCollector, Function)
 	 */
 	ClassExtensionContext(ExtensionContext parent, EngineExecutionListener engineExecutionListener,
 			ClassBasedTestDescriptor testDescriptor, JupiterConfiguration configuration,
-			ThrowableCollector throwableCollector, ExecutableInvoker executableInvoker) {
+			ThrowableCollector throwableCollector,
+			Function<ExtensionContext, ExecutableInvoker> executableInvokerFactory) {
 
 		this(parent, engineExecutionListener, testDescriptor, Lifecycle.PER_METHOD, configuration, throwableCollector,
-			executableInvoker);
+			executableInvokerFactory);
 	}
 
 	ClassExtensionContext(ExtensionContext parent, EngineExecutionListener engineExecutionListener,
 			ClassBasedTestDescriptor testDescriptor, Lifecycle lifecycle, JupiterConfiguration configuration,
-			ThrowableCollector throwableCollector, ExecutableInvoker executableInvoker) {
+			ThrowableCollector throwableCollector,
+			Function<ExtensionContext, ExecutableInvoker> executableInvokerFactory) {
 
-		super(parent, engineExecutionListener, testDescriptor, configuration, executableInvoker);
+		super(parent, engineExecutionListener, testDescriptor, configuration, executableInvokerFactory);
 
 		this.lifecycle = lifecycle;
 		this.throwableCollector = throwableCollector;
diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicExtensionContext.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicExtensionContext.java
index d0f33ae7ab01..0bc5c0542167 100644
--- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicExtensionContext.java
+++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicExtensionContext.java
@@ -13,6 +13,7 @@
 import java.lang.reflect.AnnotatedElement;
 import java.lang.reflect.Method;
 import java.util.Optional;
+import java.util.function.Function;
 
 import org.junit.jupiter.api.TestInstance;
 import org.junit.jupiter.api.extension.ExecutableInvoker;
@@ -26,8 +27,8 @@ class DynamicExtensionContext extends AbstractExtensionContext<DynamicNodeTestDe
 
 	DynamicExtensionContext(ExtensionContext parent, EngineExecutionListener engineExecutionListener,
 			DynamicNodeTestDescriptor testDescriptor, JupiterConfiguration configuration,
-			ExecutableInvoker executableInvoker) {
-		super(parent, engineExecutionListener, testDescriptor, configuration, executableInvoker);
+			Function<ExtensionContext, ExecutableInvoker> executableInvokerFactory) {
+		super(parent, engineExecutionListener, testDescriptor, configuration, executableInvokerFactory);
 	}
 
 	@Override
diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicNodeTestDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicNodeTestDescriptor.java
index 936c64603f03..ba07a11b6ec4 100644
--- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicNodeTestDescriptor.java
+++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicNodeTestDescriptor.java
@@ -46,7 +46,8 @@ public String getLegacyReportingName() {
 	@Override
 	public JupiterEngineExecutionContext prepare(JupiterEngineExecutionContext context) {
 		DynamicExtensionContext extensionContext = new DynamicExtensionContext(context.getExtensionContext(),
-			context.getExecutionListener(), this, context.getConfiguration(), new DefaultExecutableInvoker(context));
+			context.getExecutionListener(), this, context.getConfiguration(),
+			it -> new DefaultExecutableInvoker(it, context.getExtensionRegistry()));
 		// @formatter:off
 		return context.extend()
 				.withExtensionContext(extensionContext)
diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/JupiterEngineDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/JupiterEngineDescriptor.java
index 143ca2f14ab2..0f87b7a182b2 100644
--- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/JupiterEngineDescriptor.java
+++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/JupiterEngineDescriptor.java
@@ -14,7 +14,6 @@
 import static org.junit.jupiter.engine.descriptor.JupiterTestDescriptor.toExecutionMode;
 
 import org.apiguardian.api.API;
-import org.junit.jupiter.api.extension.ExecutableInvoker;
 import org.junit.jupiter.api.extension.ExtensionContext;
 import org.junit.jupiter.engine.config.JupiterConfiguration;
 import org.junit.jupiter.engine.execution.DefaultExecutableInvoker;
@@ -53,9 +52,8 @@ public JupiterEngineExecutionContext prepare(JupiterEngineExecutionContext conte
 		MutableExtensionRegistry extensionRegistry = MutableExtensionRegistry.createRegistryWithDefaultExtensions(
 			context.getConfiguration());
 		EngineExecutionListener executionListener = context.getExecutionListener();
-		ExecutableInvoker executableInvoker = new DefaultExecutableInvoker(context);
 		ExtensionContext extensionContext = new JupiterEngineExtensionContext(executionListener, this,
-			context.getConfiguration(), executableInvoker);
+			context.getConfiguration(), it -> new DefaultExecutableInvoker(it, extensionRegistry));
 
 		// @formatter:off
 		return context.extend()
diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/JupiterEngineExtensionContext.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/JupiterEngineExtensionContext.java
index 120789435b6f..988dc8ea0254 100644
--- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/JupiterEngineExtensionContext.java
+++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/JupiterEngineExtensionContext.java
@@ -13,9 +13,11 @@
 import java.lang.reflect.AnnotatedElement;
 import java.lang.reflect.Method;
 import java.util.Optional;
+import java.util.function.Function;
 
 import org.junit.jupiter.api.TestInstance.Lifecycle;
 import org.junit.jupiter.api.extension.ExecutableInvoker;
+import org.junit.jupiter.api.extension.ExtensionContext;
 import org.junit.jupiter.api.extension.TestInstances;
 import org.junit.jupiter.engine.config.JupiterConfiguration;
 import org.junit.platform.engine.EngineExecutionListener;
@@ -28,9 +30,9 @@ final class JupiterEngineExtensionContext extends AbstractExtensionContext<Jupit
 
 	JupiterEngineExtensionContext(EngineExecutionListener engineExecutionListener,
 			JupiterEngineDescriptor testDescriptor, JupiterConfiguration configuration,
-			ExecutableInvoker executableInvoker) {
+			Function<ExtensionContext, ExecutableInvoker> executableInvokerFactory) {
 
-		super(null, engineExecutionListener, testDescriptor, configuration, executableInvoker);
+		super(null, engineExecutionListener, testDescriptor, configuration, executableInvokerFactory);
 	}
 
 	@Override
diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/MethodExtensionContext.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/MethodExtensionContext.java
index 5b681457de84..6c5e2efc6fbf 100644
--- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/MethodExtensionContext.java
+++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/MethodExtensionContext.java
@@ -13,6 +13,7 @@
 import java.lang.reflect.AnnotatedElement;
 import java.lang.reflect.Method;
 import java.util.Optional;
+import java.util.function.Function;
 
 import org.junit.jupiter.api.TestInstance.Lifecycle;
 import org.junit.jupiter.api.extension.ExecutableInvoker;
@@ -34,9 +35,10 @@ final class MethodExtensionContext extends AbstractExtensionContext<TestMethodTe
 
 	MethodExtensionContext(ExtensionContext parent, EngineExecutionListener engineExecutionListener,
 			TestMethodTestDescriptor testDescriptor, JupiterConfiguration configuration,
-			ThrowableCollector throwableCollector, ExecutableInvoker executableInvoker) {
+			ThrowableCollector throwableCollector,
+			Function<ExtensionContext, ExecutableInvoker> executableInvokerFactory) {
 
-		super(parent, engineExecutionListener, testDescriptor, configuration, executableInvoker);
+		super(parent, engineExecutionListener, testDescriptor, configuration, executableInvokerFactory);
 
 		this.throwableCollector = throwableCollector;
 	}
diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestMethodTestDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestMethodTestDescriptor.java
index 80a1ee2ae876..b4f874966522 100644
--- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestMethodTestDescriptor.java
+++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestMethodTestDescriptor.java
@@ -24,7 +24,6 @@
 import org.junit.jupiter.api.extension.AfterTestExecutionCallback;
 import org.junit.jupiter.api.extension.BeforeEachCallback;
 import org.junit.jupiter.api.extension.BeforeTestExecutionCallback;
-import org.junit.jupiter.api.extension.ExecutableInvoker;
 import org.junit.jupiter.api.extension.Extension;
 import org.junit.jupiter.api.extension.ExtensionContext;
 import org.junit.jupiter.api.extension.InvocationInterceptor;
@@ -99,9 +98,9 @@ public Type getType() {
 	public JupiterEngineExecutionContext prepare(JupiterEngineExecutionContext context) {
 		MutableExtensionRegistry registry = populateNewExtensionRegistry(context);
 		ThrowableCollector throwableCollector = createThrowableCollector();
-		ExecutableInvoker executableInvoker = new DefaultExecutableInvoker(context);
 		MethodExtensionContext extensionContext = new MethodExtensionContext(context.getExtensionContext(),
-			context.getExecutionListener(), this, context.getConfiguration(), throwableCollector, executableInvoker);
+			context.getExecutionListener(), this, context.getConfiguration(), throwableCollector,
+			it -> new DefaultExecutableInvoker(it, registry));
 		throwableCollector.execute(() -> {
 			TestInstances testInstances = context.getTestInstancesProvider().getTestInstances(registry,
 				throwableCollector);
diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestTemplateExtensionContext.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestTemplateExtensionContext.java
index f7d074028c95..ae4f92b8195f 100644
--- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestTemplateExtensionContext.java
+++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestTemplateExtensionContext.java
@@ -13,6 +13,7 @@
 import java.lang.reflect.AnnotatedElement;
 import java.lang.reflect.Method;
 import java.util.Optional;
+import java.util.function.Function;
 
 import org.junit.jupiter.api.TestInstance.Lifecycle;
 import org.junit.jupiter.api.extension.ExecutableInvoker;
@@ -31,9 +32,9 @@ final class TestTemplateExtensionContext extends AbstractExtensionContext<TestTe
 
 	TestTemplateExtensionContext(ExtensionContext parent, EngineExecutionListener engineExecutionListener,
 			TestTemplateTestDescriptor testDescriptor, JupiterConfiguration configuration, TestInstances testInstances,
-			ExecutableInvoker executableInvoker) {
+			Function<ExtensionContext, ExecutableInvoker> executableInvokerFactory) {
 
-		super(parent, engineExecutionListener, testDescriptor, configuration, executableInvoker);
+		super(parent, engineExecutionListener, testDescriptor, configuration, executableInvokerFactory);
 		this.testInstances = testInstances;
 	}
 
diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestTemplateTestDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestTemplateTestDescriptor.java
index 353c5c8325f1..aa22bcbe70aa 100644
--- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestTemplateTestDescriptor.java
+++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestTemplateTestDescriptor.java
@@ -21,7 +21,6 @@
 import java.util.concurrent.atomic.AtomicInteger;
 
 import org.apiguardian.api.API;
-import org.junit.jupiter.api.extension.ExecutableInvoker;
 import org.junit.jupiter.api.extension.ExtensionContext;
 import org.junit.jupiter.api.extension.TestInstances;
 import org.junit.jupiter.api.extension.TestTemplateInvocationContext;
@@ -81,9 +80,9 @@ public JupiterEngineExecutionContext prepare(JupiterEngineExecutionContext conte
 		// The test instance should be properly maintained by the enclosing class's ExtensionContext.
 		TestInstances testInstances = context.getExtensionContext().getTestInstances().orElse(null);
 
-		ExecutableInvoker executableInvoker = new DefaultExecutableInvoker(context);
 		ExtensionContext extensionContext = new TestTemplateExtensionContext(context.getExtensionContext(),
-			context.getExecutionListener(), this, context.getConfiguration(), testInstances, executableInvoker);
+			context.getExecutionListener(), this, context.getConfiguration(), testInstances,
+			it -> new DefaultExecutableInvoker(it, registry));
 
 		// @formatter:off
 		return context.extend()
diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/DefaultExecutableInvoker.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/DefaultExecutableInvoker.java
index 5b913e70eae9..18b4c6fdf9fd 100644
--- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/DefaultExecutableInvoker.java
+++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/DefaultExecutableInvoker.java
@@ -37,10 +37,6 @@ public DefaultExecutableInvoker(ExtensionContext extensionContext, ExtensionRegi
 		this.extensionRegistry = extensionRegistry;
 	}
 
-	public DefaultExecutableInvoker(JupiterEngineExecutionContext context) {
-		this(context.getExtensionContext(), context.getExtensionRegistry());
-	}
-
 	@Override
 	public <T> T invoke(Constructor<T> constructor, Object outerInstance) {
 		Object[] arguments = resolveParameters(constructor, Optional.empty(), Optional.ofNullable(outerInstance),


Test patch:
diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/extension/ExecutableInvokerIntegrationTests.java b/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/extension/ExecutableInvokerIntegrationTests.java
index ecbe3414db0e..c3946331ec7e 100644
--- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/extension/ExecutableInvokerIntegrationTests.java
+++ b/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/extension/ExecutableInvokerIntegrationTests.java
@@ -45,8 +45,10 @@ static class ExecuteTestsTwiceTestCase {
 		static int testInvocations = 0;
 
 		@Test
-		void testWithResolvedParameter(TestInfo testInfo) {
+		void testWithResolvedParameter(TestInfo testInfo,
+				@ExtendWith(ExtensionContextParameterResolver.class) ExtensionContext extensionContext) {
 			assertNotNull(testInfo);
+			assertEquals(testInfo.getTestMethod().orElseThrow(), extensionContext.getRequiredTestMethod());
 			testInvocations++;
 		}
 
@@ -57,14 +59,15 @@ static class ExecuteConstructorTwiceTestCase {
 
 		static int constructorInvocations = 0;
 
-		public ExecuteConstructorTwiceTestCase(TestInfo testInfo) {
+		public ExecuteConstructorTwiceTestCase(TestInfo testInfo,
+				@ExtendWith(ExtensionContextParameterResolver.class) ExtensionContext extensionContext) {
 			assertNotNull(testInfo);
+			assertEquals(testInfo.getTestClass().orElseThrow(), extensionContext.getRequiredTestClass());
 			constructorInvocations++;
 		}
 
 		@Test
 		void test() {
-
 		}
 
 	}
@@ -84,9 +87,24 @@ static class ExecuteConstructorTwiceExtension implements BeforeAllCallback {
 		@Override
 		public void beforeAll(ExtensionContext context) throws Exception {
 			context.getExecutableInvoker() //
-					.invoke(context.getRequiredTestClass().getConstructor(TestInfo.class));
+					.invoke(context.getRequiredTestClass().getConstructor(TestInfo.class, ExtensionContext.class));
 		}
 
 	}
 
+	static class ExtensionContextParameterResolver implements ParameterResolver {
+
+		@Override
+		public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext)
+				throws ParameterResolutionException {
+			return ExtensionContext.class.equals(parameterContext.getParameter().getType());
+		}
+
+		@Override
+		public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext)
+				throws ParameterResolutionException {
+			return extensionContext;
+		}
+	}
+
 }
diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/ExtensionContextTests.java b/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/ExtensionContextTests.java
index 8badcd29d513..789513a890d1 100644
--- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/ExtensionContextTests.java
+++ b/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/ExtensionContextTests.java
@@ -74,7 +74,7 @@ void fromJupiterEngineDescriptor() {
 			UniqueId.root("engine", "junit-jupiter"), configuration);
 
 		JupiterEngineExtensionContext engineContext = new JupiterEngineExtensionContext(null, engineTestDescriptor,
-			configuration, null);
+			configuration, __ -> null);
 
 		// @formatter:off
 		assertAll("engineContext",
@@ -100,7 +100,7 @@ void fromClassTestDescriptor() {
 		ClassTestDescriptor outerClassDescriptor = outerClassDescriptor(nestedClassDescriptor);
 
 		ClassExtensionContext outerExtensionContext = new ClassExtensionContext(null, null, outerClassDescriptor,
-			configuration, null, null);
+			configuration, null, __ -> null);
 
 		// @formatter:off
 		assertAll("outerContext",
@@ -118,7 +118,7 @@ void fromClassTestDescriptor() {
 		// @formatter:on
 
 		ClassExtensionContext nestedExtensionContext = new ClassExtensionContext(outerExtensionContext, null,
-			nestedClassDescriptor, configuration, null, null);
+			nestedClassDescriptor, configuration, null, __ -> null);
 		assertThat(nestedExtensionContext.getParent()).containsSame(outerExtensionContext);
 	}
 
@@ -131,18 +131,18 @@ void tagsCanBeRetrievedInExtensionContext() {
 		outerClassDescriptor.addChild(methodTestDescriptor);
 
 		ClassExtensionContext outerExtensionContext = new ClassExtensionContext(null, null, outerClassDescriptor,
-			configuration, null, null);
+			configuration, null, __ -> null);
 
 		assertThat(outerExtensionContext.getTags()).containsExactly("outer-tag");
 		assertThat(outerExtensionContext.getRoot()).isSameAs(outerExtensionContext);
 
 		ClassExtensionContext nestedExtensionContext = new ClassExtensionContext(outerExtensionContext, null,
-			nestedClassDescriptor, configuration, null, null);
+			nestedClassDescriptor, configuration, null, __ -> null);
 		assertThat(nestedExtensionContext.getTags()).containsExactlyInAnyOrder("outer-tag", "nested-tag");
 		assertThat(nestedExtensionContext.getRoot()).isSameAs(outerExtensionContext);
 
 		MethodExtensionContext methodExtensionContext = new MethodExtensionContext(outerExtensionContext, null,
-			methodTestDescriptor, configuration, new OpenTest4JAwareThrowableCollector(), null);
+			methodTestDescriptor, configuration, new OpenTest4JAwareThrowableCollector(), __ -> null);
 		methodExtensionContext.setTestInstances(DefaultTestInstances.of(new OuterClass()));
 		assertThat(methodExtensionContext.getTags()).containsExactlyInAnyOrder("outer-tag", "method-tag");
 		assertThat(methodExtensionContext.getRoot()).isSameAs(outerExtensionContext);
@@ -161,11 +161,11 @@ void fromMethodTestDescriptor() {
 		Method testMethod = methodTestDescriptor.getTestMethod();
 
 		JupiterEngineExtensionContext engineExtensionContext = new JupiterEngineExtensionContext(null, engineDescriptor,
-			configuration, null);
+			configuration, __ -> null);
 		ClassExtensionContext classExtensionContext = new ClassExtensionContext(engineExtensionContext, null,
-			classTestDescriptor, configuration, null, null);
+			classTestDescriptor, configuration, null, __ -> null);
 		MethodExtensionContext methodExtensionContext = new MethodExtensionContext(classExtensionContext, null,
-			methodTestDescriptor, configuration, new OpenTest4JAwareThrowableCollector(), null);
+			methodTestDescriptor, configuration, new OpenTest4JAwareThrowableCollector(), __ -> null);
 		methodExtensionContext.setTestInstances(DefaultTestInstances.of(testInstance));
 
 		// @formatter:off
@@ -191,7 +191,7 @@ void reportEntriesArePublishedToExecutionContext() {
 		ClassTestDescriptor classTestDescriptor = outerClassDescriptor(null);
 		EngineExecutionListener engineExecutionListener = Mockito.spy(EngineExecutionListener.class);
 		ExtensionContext extensionContext = new ClassExtensionContext(null, engineExecutionListener,
-			classTestDescriptor, configuration, null, null);
+			classTestDescriptor, configuration, null, __ -> null);
 
 		Map<String, String> map1 = Collections.singletonMap("key", "value");
 		Map<String, String> map2 = Collections.singletonMap("other key", "other value");
@@ -222,9 +222,9 @@ void usingStore() {
 		TestMethodTestDescriptor methodTestDescriptor = methodDescriptor();
 		ClassTestDescriptor classTestDescriptor = outerClassDescriptor(methodTestDescriptor);
 		ExtensionContext parentContext = new ClassExtensionContext(null, null, classTestDescriptor, configuration, null,
-			null);
+			__ -> null);
 		MethodExtensionContext childContext = new MethodExtensionContext(parentContext, null, methodTestDescriptor,
-			configuration, new OpenTest4JAwareThrowableCollector(), null);
+			configuration, new OpenTest4JAwareThrowableCollector(), __ -> null);
 		childContext.setTestInstances(DefaultTestInstances.of(new OuterClass()));
 
 		ExtensionContext.Store childStore = childContext.getStore(Namespace.GLOBAL);
@@ -274,9 +274,9 @@ Stream<DynamicTest> configurationParameter() throws Exception {
 			configuration);
 
 		return Stream.of( //
-			(ExtensionContext) new JupiterEngineExtensionContext(null, engineDescriptor, echo, null), //
-			new ClassExtensionContext(null, null, classTestDescriptor, echo, null, null), //
-			new MethodExtensionContext(null, null, methodTestDescriptor, echo, null, null) //
+			(ExtensionContext) new JupiterEngineExtensionContext(null, engineDescriptor, echo, __ -> null), //
+			new ClassExtensionContext(null, null, classTestDescriptor, echo, null, __ -> null), //
+			new MethodExtensionContext(null, null, methodTestDescriptor, echo, null, __ -> null) //
 		).map(context -> dynamicTest(context.getClass().getSimpleName(),
 			() -> assertEquals(expected, context.getConfigurationParameter(key))));
 	}