Skip to content

Commit

Permalink
Convert MethodParameterResolver to generic ParameterResolver API
Browse files Browse the repository at this point in the history
Prior to this commit, the MethodParameterResolver API could only be
applied to methods. In addition, the MethodInvocationContext API
needlessly stored the method which could be retrieved directly from the
supplied Parameter.

This commit addresses these issues by converting the
MethodParameterResolver into a generic ParameterResolver API that can
be used to resolve parameters for both methods and constructors. At the
moment, however, there is no first-class support for resolving
constructor parameters in the JUnit 5 TestEngine.

Specifically, this commit includes the following changes.

 - Renamed MethodParameterResolver to ParameterResolver
 - Replaced MethodInvocationContext arguments in ParameterResolver with
   Optional<Object> for the target of method invocations
 - Moved MethodInvocationContext to the junit5-engine module for
   internal use

In addition, this commit introduces a proof of concept implementation
for constructor injection in the form of a ConstructorInvoker which is
tested in ParameterResolverTests.constructorInjectionProofOfConcept().

Closes: #267
  • Loading branch information
sbrannen committed May 25, 2016
1 parent 0ce6a31 commit 067620c
Show file tree
Hide file tree
Showing 29 changed files with 402 additions and 234 deletions.
8 changes: 4 additions & 4 deletions documentation/src/docs/asciidoc/extensions.adoc
Expand Up @@ -124,12 +124,12 @@ For concrete examples, consult the source code for the `{MockitoExtension}` and


=== Parameter Resolution === Parameter Resolution


`{MethodParameterResolver}` is an `Extension` strategy for dynamically resolving `{ParameterResolver}` defines the `Extension` API for dynamically resolving
method parameters at runtime. parameters at runtime.


If a `@Test`, `@BeforeEach`, `@AfterEach` , `@BeforeAll` or `@AfterAll` method accepts a If a `@Test`, `@BeforeEach`, `@AfterEach` , `@BeforeAll` or `@AfterAll` method accepts a
parameter, the parameter must be _resolved_ at runtime by a `MethodParameterResolver`. A parameter, the parameter must be _resolved_ at runtime by a `ParameterResolver`. A
`MethodParameterResolver` can either be built-in (see `{TestInfoParameterResolver}`) or `ParameterResolver` can either be built-in (see `{TestInfoParameterResolver}`) or
<<extension-registration,registered by the user>>. Generally speaking, parameters may be <<extension-registration,registered by the user>>. Generally speaking, parameters may be
resolved by _name_, _type_, _annotation_, or any combination thereof. For concrete examples, resolved by _name_, _type_, _annotation_, or any combination thereof. For concrete examples,
consult the source code for `{CustomTypeParameterResolver}` and consult the source code for `{CustomTypeParameterResolver}` and
Expand Down
2 changes: 1 addition & 1 deletion documentation/src/docs/asciidoc/index.adoc
Expand Up @@ -33,7 +33,7 @@ Stefan Bechtold; Sam Brannen; Johannes Link; Matthias Merdes; Marc Philipp
:ExtensionRegistrar: {master-branch}/junit5-api/src/main/java/org/junit/gen5/api/extension/ExtensionRegistrar.java[ExtensionRegistrar] :ExtensionRegistrar: {master-branch}/junit5-api/src/main/java/org/junit/gen5/api/extension/ExtensionRegistrar.java[ExtensionRegistrar]
:TestInstancePostProcessor: {master-branch}/junit5-api/src/main/java/org/junit/gen5/api/extension/TestInstancePostProcessor.java[TestInstancePostProcessor] :TestInstancePostProcessor: {master-branch}/junit5-api/src/main/java/org/junit/gen5/api/extension/TestInstancePostProcessor.java[TestInstancePostProcessor]
:JUnit5-Runner: {master-branch}/junit4-runner/src/main/java/org/junit/gen5/junit4/runner/JUnit5.java[JUnit5] :JUnit5-Runner: {master-branch}/junit4-runner/src/main/java/org/junit/gen5/junit4/runner/JUnit5.java[JUnit5]
:MethodParameterResolver: {master-branch}/junit5-api/src/main/java/org/junit/gen5/api/extension/MethodParameterResolver.java[MethodParameterResolver] :ParameterResolver: {master-branch}/junit5-api/src/main/java/org/junit/gen5/api/extension/ParameterResolver.java[ParameterResolver]
:MockitoExtension: {junit5-samples-repo}/blob/master/junit5-mockito-extension/src/main/java/com/example/mockito/MockitoExtension.java[MockitoExtension] :MockitoExtension: {junit5-samples-repo}/blob/master/junit5-mockito-extension/src/main/java/com/example/mockito/MockitoExtension.java[MockitoExtension]
:SpringExtension: https://github.com/sbrannen/spring-test-junit5/blob/master/src/main/java/org/springframework/test/context/junit5/SpringExtension.java[SpringExtension] :SpringExtension: https://github.com/sbrannen/spring-test-junit5/blob/master/src/main/java/org/springframework/test/context/junit5/SpringExtension.java[SpringExtension]
:SummaryGeneratingListener: {master-branch}/junit-launcher/src/main/java/org/junit/gen5/launcher/listeners/SummaryGeneratingListener.java[SummaryGeneratingListener] :SummaryGeneratingListener: {master-branch}/junit-launcher/src/main/java/org/junit/gen5/launcher/listeners/SummaryGeneratingListener.java[SummaryGeneratingListener]
Expand Down
10 changes: 5 additions & 5 deletions documentation/src/docs/asciidoc/writing-tests.adoc
Expand Up @@ -156,10 +156,10 @@ with the standard `Runner` implementations). As one of the major changes in JUni
methods are now permitted to have parameters. This allows for greater flexibility and methods are now permitted to have parameters. This allows for greater flexibility and
enables method-level _Dependency Injection_. enables method-level _Dependency Injection_.


`{MethodParameterResolver}` defines the API for test extensions that wish to `{ParameterResolver}` defines the API for test extensions that wish to _dynamically_
_dynamically_ resolve method parameters at runtime. If a `@Test`, `@BeforeEach`, resolve parameters at runtime. If a `@Test`, `@BeforeEach`, `@AfterEach`, `@BeforeAll`,
`@AfterEach`, `@BeforeAll`, or `@AfterAll` method accepts a parameter, the parameter must or `@AfterAll` method accepts a parameter, the parameter must be resolved at runtime by
be resolved at runtime by a registered `MethodParameterResolver`. a registered `ParameterResolver`.


There are currently two built-in resolvers that are registered automatically. There are currently two built-in resolvers that are registered automatically.


Expand Down Expand Up @@ -199,7 +199,7 @@ include::{testDir}/example/TestReporterDemo.java[tags=user_guide]
Other parameter resolvers must be explicitly enabled by registering a <<extension-model, Other parameter resolvers must be explicitly enabled by registering a <<extension-model,
test extension>> via `@ExtendWith`. test extension>> via `@ExtendWith`.


* Check out the `{MockitoExtension}` for an example of a `{MethodParameterResolver}`. While not * Check out the `{MockitoExtension}` for an example of a `{ParameterResolver}`. While not
intended to be production-ready, it demonstrates the simplicity and expressiveness of intended to be production-ready, it demonstrates the simplicity and expressiveness of
both the extension model and the parameter resolution process. `MyMockitoTest` both the extension model and the parameter resolution process. `MyMockitoTest`
demonstrates how to inject Mockito mocks into `@BeforeEach` and `@Test` methods. demonstrates how to inject Mockito mocks into `@BeforeEach` and `@Test` methods.
Expand Down
Expand Up @@ -22,19 +22,19 @@
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor; import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes; import java.nio.file.attribute.BasicFileAttributes;
import java.util.Optional;


import org.junit.gen5.api.extension.AfterEachCallback; import org.junit.gen5.api.extension.AfterEachCallback;
import org.junit.gen5.api.extension.ExtensionContext; import org.junit.gen5.api.extension.ExtensionContext;
import org.junit.gen5.api.extension.ExtensionContext.Namespace; 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.ParameterResolutionException;
import org.junit.gen5.api.extension.ParameterResolver;
import org.junit.gen5.api.extension.TestExtensionContext; import org.junit.gen5.api.extension.TestExtensionContext;


/** /**
* @since 5.0 * @since 5.0
*/ */
public class TempDirectory implements AfterEachCallback, MethodParameterResolver { public class TempDirectory implements AfterEachCallback, ParameterResolver {


@Target(ElementType.PARAMETER) @Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
Expand All @@ -45,16 +45,12 @@ public class TempDirectory implements AfterEachCallback, MethodParameterResolver
private static final String KEY = "tempDirectory"; private static final String KEY = "tempDirectory";


@Override @Override
public boolean supports(Parameter parameter, MethodInvocationContext methodInvocationContext, public boolean supports(Parameter parameter, Optional<Object> target, ExtensionContext extensionContext) {
ExtensionContext extensionContext) throws ParameterResolutionException {

return parameter.isAnnotationPresent(Root.class) && parameter.getType() == Path.class; return parameter.isAnnotationPresent(Root.class) && parameter.getType() == Path.class;
} }


@Override @Override
public Object resolve(Parameter parameter, MethodInvocationContext methodInvocationContext, public Object resolve(Parameter parameter, Optional<Object> target, ExtensionContext context) {
ExtensionContext context) throws ParameterResolutionException {

return getLocalStore(context).getOrComputeIfAbsent(KEY, key -> createTempDirectory(context)); return getLocalStore(context).getOrComputeIfAbsent(KEY, key -> createTempDirectory(context));
} }


Expand Down
Expand Up @@ -19,13 +19,13 @@
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.lang.reflect.Parameter; import java.lang.reflect.Parameter;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.util.Optional;
import java.util.function.Function; import java.util.function.Function;


import org.junit.gen5.api.Test; import org.junit.gen5.api.Test;
import org.junit.gen5.api.extension.ExtensionContext; import org.junit.gen5.api.extension.ExtensionContext;
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.ParameterResolutionException;
import org.junit.gen5.api.extension.ParameterResolver;
import org.junit.gen5.engine.junit5.extension.ExtensionRegistry; import org.junit.gen5.engine.junit5.extension.ExtensionRegistry;


/** /**
Expand All @@ -52,7 +52,7 @@ void invokingMethodsWithoutParameterDoesNotDependOnExtensions() throws Exception
} }


@Test @Test
void resolveArgumentsViaMethodParameterResolver() { void resolveArgumentsViaParameterResolver() {
testMethodWithASingleStringParameter(); testMethodWithASingleStringParameter();
thereIsAParameterResolverThatResolvesTheParameterTo("argument"); thereIsAParameterResolverThatResolvesTheParameterTo("argument");


Expand All @@ -64,15 +64,15 @@ void resolveArgumentsViaMethodParameterResolver() {
@Test @Test
void resolveMultipleArguments() { void resolveMultipleArguments() {
testMethodWith("multipleParameters", String.class, String.class, String.class); testMethodWith("multipleParameters", String.class, String.class, String.class);
register(ConfigurableMethodParameterResolver.supportsAndResolvesTo(Parameter::getName)); register(ConfigurableParameterResolver.supportsAndResolvesTo(Parameter::getName));


invokeMethod(); invokeMethod();


verify(instance).multipleParameters("arg0", "arg1", "arg2"); verify(instance).multipleParameters("arg0", "arg1", "arg2");
} }


@Test @Test
void onlyConsiderMethodParameterResolversThatSupportAParticularParameter() { void onlyConsiderParameterResolversThatSupportAParticularParameter() {
testMethodWithASingleStringParameter(); testMethodWithASingleStringParameter();
thereIsAParameterResolverThatDoesNotSupportThisParameter(); thereIsAParameterResolverThatDoesNotSupportThisParameter();
thereIsAParameterResolverThatResolvesTheParameterTo("something"); thereIsAParameterResolverThatResolvesTheParameterTo("something");
Expand All @@ -83,18 +83,18 @@ void onlyConsiderMethodParameterResolversThatSupportAParticularParameter() {
} }


@Test @Test
void passContextInformationToMethodParameterResolverMethods() { void passContextInformationToParameterResolverMethods() {
anyTestMethodWithAtLeasOneParameter(); anyTestMethodWithAtLeasOneParameter();
this.extensionContext = mock(ExtensionContext.class); this.extensionContext = mock(ExtensionContext.class);
ArgumentRecordingMethodParameterResolver extension = new ArgumentRecordingMethodParameterResolver(); ArgumentRecordingParameterResolver extension = new ArgumentRecordingParameterResolver();
register(extension); register(extension);


invokeMethod(); invokeMethod();


assertSame(extensionContext, extension.supportsArguments.extensionContext); assertSame(extensionContext, extension.supportsArguments.extensionContext);
assertSame(methodInvocationContext, extension.supportsArguments.methodInvocationContext); assertSame(instance, extension.supportsArguments.target.get());
assertSame(extensionContext, extension.resolveArguments.extensionContext); assertSame(extensionContext, extension.resolveArguments.extensionContext);
assertSame(methodInvocationContext, extension.resolveArguments.methodInvocationContext); assertSame(instance, extension.resolveArguments.target.get());
} }


@Test @Test
Expand Down Expand Up @@ -132,7 +132,7 @@ void reportThatNullIsNotAViableArgumentIfAPrimitiveTypeIsExpected() {
} }


@Test @Test
void reportIfThereIsNoMethodParameterResolverThatSupportsTheParameter() { void reportIfThereIsNoParameterResolverThatSupportsTheParameter() {
testMethodWithASingleStringParameter(); testMethodWithASingleStringParameter();


ParameterResolutionException caught = expectThrows(ParameterResolutionException.class, this::invokeMethod); ParameterResolutionException caught = expectThrows(ParameterResolutionException.class, this::invokeMethod);
Expand All @@ -141,7 +141,7 @@ void reportIfThereIsNoMethodParameterResolverThatSupportsTheParameter() {
} }


@Test @Test
void reportIfThereAreMultipleMethodParameterResolversThatSupportTheParameter() { void reportIfThereAreMultipleParameterResolversThatSupportTheParameter() {
testMethodWithASingleStringParameter(); testMethodWithASingleStringParameter();
thereIsAParameterResolverThatResolvesTheParameterTo("one"); thereIsAParameterResolverThatResolvesTheParameterTo("one");
thereIsAParameterResolverThatResolvesTheParameterTo("two"); thereIsAParameterResolverThatResolvesTheParameterTo("two");
Expand All @@ -151,7 +151,7 @@ void reportIfThereAreMultipleMethodParameterResolversThatSupportTheParameter() {
// @formatter:off // @formatter:off
assertThat(caught.getMessage()) assertThat(caught.getMessage())
.contains("parameter [java.lang.String arg0]") .contains("parameter [java.lang.String arg0]")
.contains(ConfigurableMethodParameterResolver.class.getName() + ", " + ConfigurableMethodParameterResolver.class.getName()); .contains(ConfigurableParameterResolver.class.getName() + ", " + ConfigurableParameterResolver.class.getName());
// @formatter:on // @formatter:on
} }


Expand Down Expand Up @@ -197,18 +197,18 @@ private IllegalArgumentException anyExceptionButParameterResolutionException() {
} }


private void throwDuringParameterResolution(RuntimeException parameterResolutionException) { private void throwDuringParameterResolution(RuntimeException parameterResolutionException) {
register(ConfigurableMethodParameterResolver.onAnyCallThrow(parameterResolutionException)); register(ConfigurableParameterResolver.onAnyCallThrow(parameterResolutionException));
} }


private void thereIsAParameterResolverThatResolvesTheParameterTo(Object argument) { private void thereIsAParameterResolverThatResolvesTheParameterTo(Object argument) {
register(ConfigurableMethodParameterResolver.supportsAndResolvesTo(parameter -> argument)); register(ConfigurableParameterResolver.supportsAndResolvesTo(parameter -> argument));
} }


private void thereIsAParameterResolverThatDoesNotSupportThisParameter() { private void thereIsAParameterResolverThatDoesNotSupportThisParameter() {
register(ConfigurableMethodParameterResolver.withoutSupport()); register(ConfigurableParameterResolver.withoutSupport());
} }


private void register(MethodParameterResolver extension) { private void register(ParameterResolver extension) {
extensionRegistry.registerExtension(extension, this); extensionRegistry.registerExtension(extension, this);
} }


Expand Down Expand Up @@ -254,75 +254,73 @@ public Method getMethod() {


// ------------------------------------------------------------------------- // -------------------------------------------------------------------------


static class ArgumentRecordingMethodParameterResolver implements MethodParameterResolver { static class ArgumentRecordingParameterResolver implements ParameterResolver {


Arguments supportsArguments; Arguments supportsArguments;
Arguments resolveArguments; Arguments resolveArguments;


static class Arguments { static class Arguments {


final MethodInvocationContext methodInvocationContext; final Optional<Object> target;
final ExtensionContext extensionContext; final ExtensionContext extensionContext;


Arguments(MethodInvocationContext methodInvocationContext, ExtensionContext extensionContext) { Arguments(Optional<Object> target, ExtensionContext extensionContext) {
this.methodInvocationContext = methodInvocationContext; this.target = target;
this.extensionContext = extensionContext; this.extensionContext = extensionContext;
} }
} }


@Override @Override
public boolean supports(Parameter parameter, MethodInvocationContext methodInvocationContext, public boolean supports(Parameter parameter, Optional<Object> target, ExtensionContext extensionContext) {
ExtensionContext extensionContext) throws ParameterResolutionException { supportsArguments = new Arguments(target, extensionContext);
supportsArguments = new Arguments(methodInvocationContext, extensionContext);
return true; return true;
} }


@Override @Override
public Object resolve(Parameter parameter, MethodInvocationContext methodInvocationContext, public Object resolve(Parameter parameter, Optional<Object> target, ExtensionContext extensionContext) {
ExtensionContext extensionContext) throws ParameterResolutionException { resolveArguments = new Arguments(target, extensionContext);
resolveArguments = new Arguments(methodInvocationContext, extensionContext);
return null; return null;
} }
} }


static class ConfigurableMethodParameterResolver implements MethodParameterResolver { static class ConfigurableParameterResolver implements ParameterResolver {


static MethodParameterResolver onAnyCallThrow(RuntimeException runtimeException) { static ParameterResolver onAnyCallThrow(RuntimeException runtimeException) {
return new ConfigurableMethodParameterResolver(parameter -> { return new ConfigurableParameterResolver(parameter -> {
throw runtimeException; throw runtimeException;
}, parameter -> { }, parameter -> {
throw runtimeException; throw runtimeException;
}); });
} }


static MethodParameterResolver supportsAndResolvesTo(Function<Parameter, Object> resolve) { static ParameterResolver supportsAndResolvesTo(Function<Parameter, Object> resolve) {
return new ConfigurableMethodParameterResolver(parameter -> true, resolve); return new ConfigurableParameterResolver(parameter -> true, resolve);
} }


static MethodParameterResolver withoutSupport() { static ParameterResolver withoutSupport() {
return new ConfigurableMethodParameterResolver(parameter -> false, parameter -> { return new ConfigurableParameterResolver(parameter -> false, parameter -> {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
}); });
} }


private final Function<Parameter, Boolean> supports; private final Function<Parameter, Boolean> supports;
private final Function<Parameter, Object> resolve; private final Function<Parameter, Object> resolve;


private ConfigurableMethodParameterResolver(Function<Parameter, Boolean> supports, private ConfigurableParameterResolver(Function<Parameter, Boolean> supports,
Function<Parameter, Object> resolve) { Function<Parameter, Object> resolve) {
this.supports = supports; this.supports = supports;
this.resolve = resolve; this.resolve = resolve;
} }


@Override @Override
public boolean supports(Parameter parameter, MethodInvocationContext methodInvocationContext, public boolean supports(Parameter parameter, Optional<Object> target, ExtensionContext extensionContext)
ExtensionContext extensionContext) throws ParameterResolutionException { throws ParameterResolutionException {
return supports.apply(parameter); return supports.apply(parameter);
} }


@Override @Override
public Object resolve(Parameter parameter, MethodInvocationContext methodInvocationContext, public Object resolve(Parameter parameter, Optional<Object> target, ExtensionContext extensionContext)
ExtensionContext extensionContext) throws ParameterResolutionException { throws ParameterResolutionException {
return resolve.apply(parameter); return resolve.apply(parameter);
} }
} }
Expand Down
Expand Up @@ -11,28 +11,24 @@
package org.junit.gen5.engine.junit5.execution.injection.sample; package org.junit.gen5.engine.junit5.execution.injection.sample;


import java.lang.reflect.Parameter; import java.lang.reflect.Parameter;
import java.util.Optional;


import org.junit.gen5.api.extension.ExtensionContext; import org.junit.gen5.api.extension.ExtensionContext;
import org.junit.gen5.api.extension.MethodInvocationContext; import org.junit.gen5.api.extension.ParameterResolver;
import org.junit.gen5.api.extension.MethodParameterResolver;
import org.junit.gen5.commons.util.ReflectionUtils; import org.junit.gen5.commons.util.ReflectionUtils;


/** /**
* @since 5.0 * @since 5.0
*/ */
public class CustomAnnotationParameterResolver implements MethodParameterResolver { public class CustomAnnotationParameterResolver implements ParameterResolver {


@Override @Override
public boolean supports(Parameter parameter, MethodInvocationContext methodInvocationContext, public boolean supports(Parameter parameter, Optional<Object> target, ExtensionContext extensionContext) {
ExtensionContext extensionContext) {

return parameter.isAnnotationPresent(CustomAnnotation.class); return parameter.isAnnotationPresent(CustomAnnotation.class);
} }


@Override @Override
public Object resolve(Parameter parameter, MethodInvocationContext methodInvocationContext, public Object resolve(Parameter parameter, Optional<Object> target, ExtensionContext extensionContext) {
ExtensionContext extensionContext) {

return ReflectionUtils.newInstance(parameter.getType()); return ReflectionUtils.newInstance(parameter.getType());
} }


Expand Down
Expand Up @@ -11,27 +11,23 @@
package org.junit.gen5.engine.junit5.execution.injection.sample; package org.junit.gen5.engine.junit5.execution.injection.sample;


import java.lang.reflect.Parameter; import java.lang.reflect.Parameter;
import java.util.Optional;


import org.junit.gen5.api.extension.ExtensionContext; import org.junit.gen5.api.extension.ExtensionContext;
import org.junit.gen5.api.extension.MethodInvocationContext; import org.junit.gen5.api.extension.ParameterResolver;
import org.junit.gen5.api.extension.MethodParameterResolver;


/** /**
* @since 5.0 * @since 5.0
*/ */
public class CustomTypeParameterResolver implements MethodParameterResolver { public class CustomTypeParameterResolver implements ParameterResolver {


@Override @Override
public boolean supports(Parameter parameter, MethodInvocationContext methodInvocationContext, public boolean supports(Parameter parameter, Optional<Object> target, ExtensionContext extensionContext) {
ExtensionContext extensionContext) {

return parameter.getType().equals(CustomType.class); return parameter.getType().equals(CustomType.class);
} }


@Override @Override
public Object resolve(Parameter parameter, MethodInvocationContext methodInvocationContext, public Object resolve(Parameter parameter, Optional<Object> target, ExtensionContext extensionContext) {
ExtensionContext extensionContext) {

return new CustomType(); return new CustomType();
} }


Expand Down

0 comments on commit 067620c

Please sign in to comment.