diff --git a/documentation/src/docs/asciidoc/user-guide/extensions.adoc b/documentation/src/docs/asciidoc/user-guide/extensions.adoc index 4d7c5020d6ad..0207b1319b18 100644 --- a/documentation/src/docs/asciidoc/user-guide/extensions.adoc +++ b/documentation/src/docs/asciidoc/user-guide/extensions.adoc @@ -484,6 +484,57 @@ constructor invocations, using the `{ExecutableInvoker}` available via the `getExecutableInvoker()` method in the `ExtensionContext`. ==== +[[extensions-parameter-resolution-conflicts]] +==== Parameter Conflicts + +If multiple implementations of `ParameterResolver` that support the same type are +registered for a test, a `ParameterResolutionException` will be thrown, with a +message to indicate that competing resolvers have been discovered. See the following +example: + +[source,java,indent=0] +.Conflicting parameter resolution due to multiple resolvers claiming support for integers +---- +include::{testDir}/example/extensions/ParameterResolverConflictDemo.java[tags=user_guide] +---- + +If the conflicting `ParameterResolver` implementations are applied to different test +methods as shown in the following example, no conflict occurs. + +[source,java,indent=0] +.Fine-grained registration to avoid conflict +---- +include::{testDir}/example/extensions/ParameterResolverNoConflictDemo.java[tags=user_guide] +---- + +If the conflicting `ParameterResolver` implementations need to be applied to the same test +method, you can implement a custom type or custom annotation as illustrated by +`{CustomTypeParameterResolver}` and `{CustomAnnotationParameterResolver}`, respectively. + +[source,java,indent=0] +.Custom type to resolve duplicate types +---- +include::{testDir}/example/extensions/ParameterResolverCustomTypeDemo.java[tags=user_guide] +---- + +A custom annotation makes the duplicate type distinguishable from its counterpart: + +[source,java,indent=0] +.Custom annotation to resolve duplicate types +---- +include::{testDir}/example/extensions/ParameterResolverCustomAnnotationDemo.java[tags=user_guide] +---- + +JUnit includes some built-in parameter resolvers that can cause conflicts if a resolver +attempts to claim their supported types. For example, `{TestInfo}` provides metadata about +tests. See <> for details. Third-party frameworks such +as Spring may also define parameter resolvers. Apply one of the techniques in this section +to resolve any conflicts. + +Parameterized tests are another potential source of conflict. Ensure that tests annotated +with `@ParameterizedTest` are not also annotated with `@Test` and see +<> for more details. + [[extensions-test-result-processing]] === Test Result Processing diff --git a/documentation/src/test/java/example/extensions/ParameterResolverConflictDemo.java b/documentation/src/test/java/example/extensions/ParameterResolverConflictDemo.java new file mode 100644 index 000000000000..c20c17ce9d86 --- /dev/null +++ b/documentation/src/test/java/example/extensions/ParameterResolverConflictDemo.java @@ -0,0 +1,62 @@ +/* + * 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 example.extensions; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import extensions.ExpectToFail; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ParameterContext; +import org.junit.jupiter.api.extension.ParameterResolver; + +// tag::user_guide[] +public class ParameterResolverConflictDemo { + + // end::user_guide[] + @ExpectToFail + // tag::user_guide[] + @Test + @ExtendWith({ FirstIntegerResolver.class, SecondIntegerResolver.class }) + void testInt(int i) { + // Test will not run due to ParameterResolutionException + assertEquals(1, i); + } + + static class FirstIntegerResolver implements ParameterResolver { + + @Override + public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { + return parameterContext.getParameter().getType() == int.class; + } + + @Override + public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { + return 1; + } + } + + static class SecondIntegerResolver implements ParameterResolver { + + @Override + public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { + return parameterContext.getParameter().getType() == int.class; + } + + @Override + public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { + return 2; + } + } +} +// end::user_guide[] diff --git a/documentation/src/test/java/example/extensions/ParameterResolverCustomAnnotationDemo.java b/documentation/src/test/java/example/extensions/ParameterResolverCustomAnnotationDemo.java new file mode 100644 index 000000000000..8e68516ce659 --- /dev/null +++ b/documentation/src/test/java/example/extensions/ParameterResolverCustomAnnotationDemo.java @@ -0,0 +1,74 @@ +/* + * 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 example.extensions; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ParameterContext; +import org.junit.jupiter.api.extension.ParameterResolver; + +// tag::user_guide[] +public class ParameterResolverCustomAnnotationDemo { + + @Test + void testInt(@FirstInteger Integer first, @SecondInteger Integer second) { + assertEquals(1, first); + assertEquals(2, second); + } + + @Target(ElementType.PARAMETER) + @Retention(RetentionPolicy.RUNTIME) + @ExtendWith(FirstInteger.Extension.class) + public @interface FirstInteger { + + class Extension implements ParameterResolver { + + @Override + public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { + return parameterContext.getParameter().getType().equals(Integer.class) + && !parameterContext.isAnnotated(SecondInteger.class); + } + + @Override + public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { + return 1; + } + } + } + + @Target(ElementType.PARAMETER) + @Retention(RetentionPolicy.RUNTIME) + @ExtendWith(SecondInteger.Extension.class) + public @interface SecondInteger { + + class Extension implements ParameterResolver { + + @Override + public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { + return parameterContext.isAnnotated(SecondInteger.class); + } + + @Override + public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { + return 2; + } + } + } +} +// end::user_guide[] diff --git a/documentation/src/test/java/example/extensions/ParameterResolverCustomTypeDemo.java b/documentation/src/test/java/example/extensions/ParameterResolverCustomTypeDemo.java new file mode 100644 index 000000000000..c0b5b374c45d --- /dev/null +++ b/documentation/src/test/java/example/extensions/ParameterResolverCustomTypeDemo.java @@ -0,0 +1,67 @@ +/* + * 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 example.extensions; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ParameterContext; +import org.junit.jupiter.api.extension.ParameterResolver; + +// tag::user_guide[] +public class ParameterResolverCustomTypeDemo { + + @Test + @ExtendWith({ FirstIntegerResolver.class, SecondIntegerResolver.class }) + void testInt(Integer i, WrappedInteger wrappedInteger) { + assertEquals(1, i); + assertEquals(2, wrappedInteger.value); + } + + static class FirstIntegerResolver implements ParameterResolver { + + @Override + public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { + return parameterContext.getParameter().getType().equals(Integer.class); + } + + @Override + public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { + return 1; + } + } + + static class SecondIntegerResolver implements ParameterResolver { + + @Override + public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { + return parameterContext.getParameter().getType().equals(WrappedInteger.class); + } + + @Override + public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { + return new WrappedInteger(2); + } + } + + static class WrappedInteger { + + private final int value; + + public WrappedInteger(int value) { + this.value = value; + } + + } +} +// end::user_guide[] diff --git a/documentation/src/test/java/example/extensions/ParameterResolverNoConflictDemo.java b/documentation/src/test/java/example/extensions/ParameterResolverNoConflictDemo.java new file mode 100644 index 000000000000..8f306a728632 --- /dev/null +++ b/documentation/src/test/java/example/extensions/ParameterResolverNoConflictDemo.java @@ -0,0 +1,62 @@ +/* + * 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 example.extensions; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ParameterContext; +import org.junit.jupiter.api.extension.ParameterResolver; + +// tag::user_guide[] +public class ParameterResolverNoConflictDemo { + + @Test + @ExtendWith(FirstIntegerResolver.class) + void firstResolution(int i) { + assertEquals(1, i); + } + + @Test + @ExtendWith(SecondIntegerResolver.class) + void secondResolution(int i) { + assertEquals(2, i); + } + + static class FirstIntegerResolver implements ParameterResolver { + + @Override + public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { + return parameterContext.getParameter().getType() == int.class; + } + + @Override + public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { + return 1; + } + } + + static class SecondIntegerResolver implements ParameterResolver { + + @Override + public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { + return parameterContext.getParameter().getType() == int.class; + } + + @Override + public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { + return 2; + } + } +} +// end::user_guide[]