From 2d952e83793a8979f75be86caf328808b85b836c Mon Sep 17 00:00:00 2001 From: Martin Kouba Date: Tue, 27 Aug 2024 09:58:45 +0200 Subject: [PATCH] QuarkusComponentTest: programmatic lookup improvements - handle programmatic lookup injection points specifically: register type arguments as tested components and consider these types in the unused beans removal exclusion - support programmatic lookup in test method params (`Instance<>`, `@All List<>`) - add "Tested components" section in the docs - related to #42643 --- .../src/main/asciidoc/testing-components.adoc | 13 ++ .../io/quarkus/arc/impl/InstanceImpl.java | 4 +- .../QuarkusComponentTestConfiguration.java | 89 +++++++++-- .../QuarkusComponentTestExtension.java | 151 +++++++++++++----- .../QuarkusComponentTestExtensionBuilder.java | 6 +- .../declarative/InstanceComponentTest.java | 23 +++ .../declarative/InstanceInterfaceTest.java | 24 +++ .../declarative/ListAllComponentTest.java | 27 ++++ .../declarative/ListAllInterfaceTest.java | 27 ++++ .../declarative/ListAllMockTest.java | 5 - .../test/component/declarative/SomeBean.java | 20 +++ .../component/declarative/SomeInterface.java | 5 + .../paraminject/ParameterInjectionTest.java | 8 +- 13 files changed, 336 insertions(+), 66 deletions(-) create mode 100644 test-framework/junit5-component/src/test/java/io/quarkus/test/component/declarative/InstanceComponentTest.java create mode 100644 test-framework/junit5-component/src/test/java/io/quarkus/test/component/declarative/InstanceInterfaceTest.java create mode 100644 test-framework/junit5-component/src/test/java/io/quarkus/test/component/declarative/ListAllComponentTest.java create mode 100644 test-framework/junit5-component/src/test/java/io/quarkus/test/component/declarative/ListAllInterfaceTest.java create mode 100644 test-framework/junit5-component/src/test/java/io/quarkus/test/component/declarative/SomeBean.java create mode 100644 test-framework/junit5-component/src/test/java/io/quarkus/test/component/declarative/SomeInterface.java diff --git a/docs/src/main/asciidoc/testing-components.adoc b/docs/src/main/asciidoc/testing-components.adoc index 3e84db7c2f4de..466042404af30 100644 --- a/docs/src/main/asciidoc/testing-components.adoc +++ b/docs/src/main/asciidoc/testing-components.adoc @@ -177,6 +177,19 @@ Dependent beans injected into the fields and test method arguments are correctly NOTE: Arguments of a `@ParameterizedTest` method that are provided by an `ArgumentsProvider`, for example with `@org.junit.jupiter.params.provider.ValueArgumentsProvider`, must be annotated with `@SkipInject`. +=== Tested components + +The initial set of tested components is derived from the test class: + +1. The types of all fields annotated with `@jakarta.inject.Inject` are considered the component types. +2. The types of test methods parameters that are not annotated with `@InjectMock`, `@SkipInject`, or `@org.mockito.Mock` are also considered the component types. +3. If `@QuarkusComponentTest#addNestedClassesAsComponents()` is set to `true` (default) then all static nested classes declared on the test class are components too. + +NOTE: `@Inject Instance` and `@Inject @All List` injection points are handled specifically. The actual type argument is registered as a component. However, if the type argument is an interface the implementations _are not registered_ automatically. + +Additional components classes can be set using `@QuarkusComponentTest#value()` or `QuarkusComponentTestExtensionBuilder#addComponentClasses()`. + + [[auto_mocking]] === Auto Mocking Unsatisfied Dependencies diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/InstanceImpl.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/InstanceImpl.java index 2ce4864251c0a..6ce83ca45b3c9 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/InstanceImpl.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/InstanceImpl.java @@ -119,7 +119,7 @@ public T get() { return new Guard<>(result); } - static InstanceImpl forGlobalEntrypoint(Type requiredType, Set requiredQualifiers) { + public static InstanceImpl forGlobalEntrypoint(Type requiredType, Set requiredQualifiers) { return new InstanceImpl<>(new CreationalContextImpl<>(null), requiredType, requiredQualifiers, null, null, Collections.emptySet(), null, -1, false, true); } @@ -309,7 +309,7 @@ private T getInternal() { return getBeanInstance(bean()); } - void destroy() { + public void destroy() { creationalContext.release(); } diff --git a/test-framework/junit5-component/src/main/java/io/quarkus/test/component/QuarkusComponentTestConfiguration.java b/test-framework/junit5-component/src/main/java/io/quarkus/test/component/QuarkusComponentTestConfiguration.java index 1d6f226a713db..ef3e8ec2ab65b 100644 --- a/test-framework/junit5-component/src/main/java/io/quarkus/test/component/QuarkusComponentTestConfiguration.java +++ b/test-framework/junit5-component/src/main/java/io/quarkus/test/component/QuarkusComponentTestConfiguration.java @@ -1,14 +1,21 @@ package io.quarkus.test.component; +import java.lang.reflect.Array; import java.lang.reflect.Field; +import java.lang.reflect.GenericArrayType; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.lang.reflect.Parameter; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; +import java.lang.reflect.WildcardType; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.function.Consumer; import jakarta.enterprise.event.Event; @@ -20,6 +27,7 @@ import org.eclipse.microprofile.config.spi.Converter; import org.jboss.logging.Logger; +import org.mockito.Mock; import io.quarkus.arc.InjectableInstance; import io.quarkus.arc.processor.AnnotationsTransformer; @@ -52,14 +60,14 @@ class QuarkusComponentTestConfiguration { new ZoneIdConverter(), new LevelConverter()); - static final QuarkusComponentTestConfiguration DEFAULT = new QuarkusComponentTestConfiguration(Map.of(), List.of(), + static final QuarkusComponentTestConfiguration DEFAULT = new QuarkusComponentTestConfiguration(Map.of(), Set.of(), List.of(), false, true, QuarkusComponentTestExtensionBuilder.DEFAULT_CONFIG_SOURCE_ORDINAL, List.of(), DEFAULT_CONVERTERS, null); private static final Logger LOG = Logger.getLogger(QuarkusComponentTestConfiguration.class); final Map configProperties; - final List> componentClasses; + final Set> componentClasses; final List> mockConfigurators; final boolean useDefaultConfigProperties; final boolean addNestedClassesAsComponents; @@ -68,7 +76,7 @@ class QuarkusComponentTestConfiguration { final List> configConverters; final Consumer configBuilderCustomizer; - QuarkusComponentTestConfiguration(Map configProperties, List> componentClasses, + QuarkusComponentTestConfiguration(Map configProperties, Set> componentClasses, List> mockConfigurators, boolean useDefaultConfigProperties, boolean addNestedClassesAsComponents, int configSourceOrdinal, List annotationsTransformers, List> configConverters, @@ -124,8 +132,18 @@ QuarkusComponentTestConfiguration update(Class testClass) { while (current != null && current != Object.class) { // All fields annotated with @Inject represent component classes for (Field field : current.getDeclaredFields()) { - if (field.isAnnotationPresent(Inject.class) && !resolvesToBuiltinBean(field.getType())) { - componentClasses.add(field.getType()); + if (field.isAnnotationPresent(Inject.class)) { + if (Instance.class.isAssignableFrom(field.getType()) + || QuarkusComponentTestExtension.isListAllInjectionPoint(field.getGenericType(), + field.getAnnotations(), + field)) { + // Special handling for Instance and @All List + componentClasses + .add(getRawType( + QuarkusComponentTestExtension.getFirstActualTypeArgument(field.getGenericType()))); + } else if (!resolvesToBuiltinBean(field.getType())) { + componentClasses.add(field.getType()); + } } } // All static nested classes declared on the test class are components @@ -138,17 +156,26 @@ QuarkusComponentTestConfiguration update(Class testClass) { } // All params of test methods but: // - not covered by built-in extensions - // - not annotated with @InjectMock - // - not annotated with @SkipInject + // - not annotated with @InjectMock, @SkipInject, @org.mockito.Mock for (Method method : current.getDeclaredMethods()) { if (QuarkusComponentTestExtension.isTestMethod(method)) { for (Parameter param : method.getParameters()) { if (QuarkusComponentTestExtension.BUILTIN_PARAMETER.test(param) || param.isAnnotationPresent(InjectMock.class) - || param.isAnnotationPresent(SkipInject.class)) { + || param.isAnnotationPresent(SkipInject.class) + || param.isAnnotationPresent(Mock.class)) { continue; } - componentClasses.add(param.getType()); + if (Instance.class.isAssignableFrom(param.getType()) + || QuarkusComponentTestExtension.isListAllInjectionPoint(param.getParameterizedType(), + param.getAnnotations(), + param)) { + // Special handling for Instance and @All List + componentClasses.add(getRawType( + QuarkusComponentTestExtension.getFirstActualTypeArgument(param.getParameterizedType()))); + } else { + componentClasses.add(param.getType()); + } } } } @@ -161,9 +188,8 @@ QuarkusComponentTestConfiguration update(Class testClass) { configProperties.put(testConfigProperty.key(), testConfigProperty.value()); } - return new QuarkusComponentTestConfiguration(Map.copyOf(configProperties), List.copyOf(componentClasses), - this.mockConfigurators, - useDefaultConfigProperties, addNestedClassesAsComponents, configSourceOrdinal, + return new QuarkusComponentTestConfiguration(Map.copyOf(configProperties), Set.copyOf(componentClasses), + this.mockConfigurators, useDefaultConfigProperties, addNestedClassesAsComponents, configSourceOrdinal, List.copyOf(annotationsTransformers), List.copyOf(configConverters), configBuilderCustomizer); } @@ -188,4 +214,43 @@ private static boolean resolvesToBuiltinBean(Class rawType) { || BeanManager.class.equals(rawType); } + @SuppressWarnings("unchecked") + static Class getRawType(Type type) { + if (type instanceof Class) { + return (Class) type; + } + if (type instanceof ParameterizedType) { + final ParameterizedType parameterizedType = (ParameterizedType) type; + if (parameterizedType.getRawType() instanceof Class) { + return (Class) parameterizedType.getRawType(); + } + } + if (type instanceof TypeVariable) { + TypeVariable variable = (TypeVariable) type; + Type[] bounds = variable.getBounds(); + return getBound(bounds); + } + if (type instanceof WildcardType) { + WildcardType wildcard = (WildcardType) type; + return getBound(wildcard.getUpperBounds()); + } + if (type instanceof GenericArrayType) { + GenericArrayType genericArrayType = (GenericArrayType) type; + Class rawType = getRawType(genericArrayType.getGenericComponentType()); + if (rawType != null) { + return (Class) Array.newInstance(rawType, 0).getClass(); + } + } + return null; + } + + @SuppressWarnings("unchecked") + private static Class getBound(Type[] bounds) { + if (bounds.length == 0) { + return (Class) Object.class; + } else { + return getRawType(bounds[0]); + } + } + } diff --git a/test-framework/junit5-component/src/main/java/io/quarkus/test/component/QuarkusComponentTestExtension.java b/test-framework/junit5-component/src/main/java/io/quarkus/test/component/QuarkusComponentTestExtension.java index 663cb0dd2d733..d45e7bb5d90b7 100644 --- a/test-framework/junit5-component/src/main/java/io/quarkus/test/component/QuarkusComponentTestExtension.java +++ b/test-framework/junit5-component/src/main/java/io/quarkus/test/component/QuarkusComponentTestExtension.java @@ -44,6 +44,7 @@ import jakarta.enterprise.context.Dependent; import jakarta.enterprise.inject.AmbiguousResolutionException; import jakarta.enterprise.inject.Default; +import jakarta.enterprise.inject.Instance; import jakarta.enterprise.inject.spi.Bean; import jakarta.enterprise.inject.spi.BeanManager; import jakarta.enterprise.inject.spi.InjectionPoint; @@ -95,6 +96,7 @@ import io.quarkus.arc.ComponentsProvider; import io.quarkus.arc.InstanceHandle; import io.quarkus.arc.Unremovable; +import io.quarkus.arc.impl.InstanceImpl; import io.quarkus.arc.processor.Annotations; import io.quarkus.arc.processor.AnnotationsTransformer; import io.quarkus.arc.processor.BeanArchives; @@ -202,7 +204,7 @@ public QuarkusComponentTestExtension() { * @param additionalComponentClasses */ public QuarkusComponentTestExtension(Class... additionalComponentClasses) { - this(new QuarkusComponentTestConfiguration(Map.of(), List.of(additionalComponentClasses), + this(new QuarkusComponentTestConfiguration(Map.of(), Set.of(additionalComponentClasses), List.of(), false, true, QuarkusComponentTestExtensionBuilder.DEFAULT_CONFIG_SOURCE_ORDINAL, List.of(), List.of(), null)); } @@ -286,13 +288,8 @@ && isTestMethod(parameterContext.getDeclaringExecutable()) BeanManager beanManager = Arc.container().beanManager(); java.lang.reflect.Type requiredType = parameterContext.getParameter().getParameterizedType(); Annotation[] qualifiers = getQualifiers(parameterContext.getAnnotatedElement(), beanManager); - if (qualifiers.length > 0 && Arrays.stream(qualifiers).anyMatch(All.Literal.INSTANCE::equals)) { - // @All List<> - if (isListRequiredType(requiredType)) { - return true; - } else { - throw new IllegalStateException("Invalid injection point type: " + parameterContext.getParameter()); - } + if (isListAllInjectionPoint(requiredType, qualifiers, parameterContext.getParameter())) { + return true; } else { try { Bean bean = beanManager.resolve(beanManager.getBeans(requiredType, qualifiers)); @@ -322,14 +319,22 @@ && isTestMethod(parameterContext.getDeclaringExecutable()) public Object resolveParameter(ParameterContext parameterContext, ExtensionContext context) throws ParameterResolutionException { @SuppressWarnings("unchecked") - List> injectedParams = store(context).get(KEY_INJECTED_PARAMS, List.class); + List injectedParams = store(context).get(KEY_INJECTED_PARAMS, List.class); ArcContainer container = Arc.container(); BeanManager beanManager = container.beanManager(); java.lang.reflect.Type requiredType = parameterContext.getParameter().getParameterizedType(); Annotation[] qualifiers = getQualifiers(parameterContext.getAnnotatedElement(), beanManager); - if (qualifiers.length > 0 && Arrays.stream(qualifiers).anyMatch(All.Literal.INSTANCE::equals)) { - // Special handling for @Injec @All List<> - return handleListAll(requiredType, qualifiers, container, injectedParams); + if (Instance.class.isAssignableFrom(parameterContext.getParameter().getType())) { + InstanceImpl instance = InstanceImpl.forGlobalEntrypoint(getFirstActualTypeArgument(requiredType), + Set.of(qualifiers)); + injectedParams.add(instance); + return instance; + } else if (isListAllInjectionPoint(requiredType, qualifiers, parameterContext.getParameter())) { + // Special handling for @Inject @All List<> + Collection> unsetHandles = new ArrayList<>(); + Object ret = handleListAll(requiredType, qualifiers, container, unsetHandles); + unsetHandles.forEach(injectedParams::add); + return ret; } else { InstanceHandle handle = container.instance(requiredType, qualifiers); injectedParams.add(handle); @@ -339,14 +344,21 @@ public Object resolveParameter(ParameterContext parameterContext, ExtensionConte private void destroyDependentTestMethodParams(ExtensionContext context) { @SuppressWarnings("unchecked") - List> injectedParams = store(context).get(KEY_INJECTED_PARAMS, List.class); - for (InstanceHandle handle : injectedParams) { - if (handle.getBean() != null && handle.getBean().getScope().equals(Dependent.class)) { - try { - handle.destroy(); - } catch (Exception e) { - LOG.errorf(e, "Unable to destroy the injected %s", handle.getBean()); + List injectedParams = store(context).get(KEY_INJECTED_PARAMS, List.class); + for (Object param : injectedParams) { + if (param instanceof InstanceHandle) { + @SuppressWarnings("resource") + InstanceHandle handle = (InstanceHandle) param; + if (handle.getBean() != null && handle.getBean().getScope().equals(Dependent.class)) { + try { + handle.destroy(); + } catch (Exception e) { + LOG.errorf(e, "Unable to destroy the injected %s", handle.getBean()); + } } + } else if (param instanceof InstanceImpl) { + InstanceImpl instance = (InstanceImpl) param; + instance.destroy(); } } injectedParams.clear(); @@ -514,6 +526,11 @@ private ClassLoader initArcContainer(ExtensionContext extensionContext, QuarkusC throw new IllegalStateException("An error occured during ArC shutdown: " + e); } + if (LOG.isDebugEnabled()) { + LOG.debugf("Tested components: \n - %s", + configuration.componentClasses.stream().map(Object::toString).collect(Collectors.joining("\n - "))); + } + // Build index IndexView index; try { @@ -552,21 +569,21 @@ private ClassLoader initArcContainer(ExtensionContext extensionContext, QuarkusC .setName(testClass.getName().replace('.', '_')) .addRemovalExclusion(b -> { // Do not remove beans: - // 1. Injected in the test class or in a test method parameter - // 2. Annotated with @Unremovable + // 1. Annotated with @Unremovable + // 2. Injected in the test class or in a test method parameter if (b.getTarget().isPresent() && b.getTarget().get().hasDeclaredAnnotation(Unremovable.class)) { return true; } for (Field injectionPoint : injectFields) { - if (beanResolver.get().matches(b, Types.jandexType(injectionPoint.getGenericType()), - getQualifiers(injectionPoint, qualifiers))) { + if (injectionPointMatchesBean(injectionPoint.getGenericType(), injectionPoint, qualifiers, + beanResolver.get(), b)) { return true; } } for (Parameter param : injectParams) { - if (beanResolver.get().matches(b, Types.jandexType(param.getParameterizedType()), - getQualifiers(param, qualifiers))) { + if (injectionPointMatchesBean(param.getParameterizedType(), param, qualifiers, beanResolver.get(), + b)) { return true; } } @@ -1078,7 +1095,7 @@ private List findMethods(Class testClass, Predicate methodPre static class FieldInjector { private final Field field; - private final List> unsetHandles; + private final Runnable unsetAction; public FieldInjector(Field field, Object testInstance) throws Exception { this.field = field; @@ -1090,14 +1107,16 @@ public FieldInjector(Field field, Object testInstance) throws Exception { Object injectedInstance; - if (qualifiers.length > 0 && Arrays.stream(qualifiers).anyMatch(All.Literal.INSTANCE::equals)) { + if (Instance.class.isAssignableFrom(QuarkusComponentTestConfiguration.getRawType(requiredType))) { + InstanceImpl instance = InstanceImpl.forGlobalEntrypoint(getFirstActualTypeArgument(requiredType), + Set.of(qualifiers)); + injectedInstance = instance; + unsetAction = instance::destroy; + } else if (isListAllInjectionPoint(requiredType, qualifiers, field)) { // Special handling for @Injec @All List - if (isListRequiredType(requiredType)) { - unsetHandles = new ArrayList<>(); - injectedInstance = handleListAll(requiredType, qualifiers, container, unsetHandles); - } else { - throw new IllegalStateException("Invalid injection point type: " + field); - } + List> unsetHandles = new ArrayList<>(); + injectedInstance = handleListAll(requiredType, qualifiers, container, unsetHandles); + unsetAction = () -> destroyDependentHandles(unsetHandles); } else { InstanceHandle handle = container.instance(requiredType, qualifiers); if (field.isAnnotationPresent(Inject.class)) { @@ -1124,7 +1143,7 @@ public FieldInjector(Field field, Object testInstance) throws Exception { } } injectedInstance = handle.get(); - unsetHandles = List.of(handle); + unsetAction = () -> destroyDependentHandles(List.of(handle)); } if (!field.canAccess(testInstance)) { @@ -1135,7 +1154,14 @@ public FieldInjector(Field field, Object testInstance) throws Exception { } void unset(Object testInstance) throws Exception { - for (InstanceHandle handle : unsetHandles) { + if (unsetAction != null) { + unsetAction.run(); + } + field.set(testInstance, null); + } + + void destroyDependentHandles(List> handles) { + for (InstanceHandle handle : handles) { if (handle.getBean() != null && handle.getBean().getScope().equals(Dependent.class)) { try { handle.destroy(); @@ -1144,13 +1170,12 @@ void unset(Object testInstance) throws Exception { } } } - field.set(testInstance, null); } } private static Object handleListAll(java.lang.reflect.Type requiredType, Annotation[] qualifiers, ArcContainer container, - Collection> cleanupHandles) { + Collection> unsetHandles) { // Remove @All and add @Default if empty Set qualifiersSet = new HashSet<>(); Collections.addAll(qualifiersSet, qualifiers); @@ -1160,8 +1185,8 @@ private static Object handleListAll(java.lang.reflect.Type requiredType, Annotat } else { qualifiers = qualifiersSet.toArray(new Annotation[] {}); } - List> handles = container.listAll(getListRequiredType(requiredType), qualifiers); - cleanupHandles.addAll(handles); + List> handles = container.listAll(getFirstActualTypeArgument(requiredType), qualifiers); + unsetHandles.addAll(handles); return isTypeArgumentInstanceHandle(requiredType) ? handles : handles.stream().map(InstanceHandle::get).collect(Collectors.toUnmodifiableList()); } @@ -1183,13 +1208,32 @@ private static boolean isListRequiredType(java.lang.reflect.Type type) { return false; } - private static java.lang.reflect.Type getListRequiredType(java.lang.reflect.Type requiredType) { + static boolean isListAllInjectionPoint(java.lang.reflect.Type requiredType, Annotation[] qualifiers, + AnnotatedElement annotatedElement) { + if (qualifiers.length > 0 && Arrays.stream(qualifiers).anyMatch(All.Literal.INSTANCE::equals)) { + if (!isListRequiredType(requiredType)) { + throw new IllegalStateException("Invalid injection point type: " + annotatedElement); + } + return true; + } + return false; + } + + static final DotName ALL_NAME = DotName.createSimple(All.class); + + static void adaptListAllQualifiers(Set qualifiers) { + // Remove @All and add @Default if empty + qualifiers.removeIf(a -> a.name().equals(ALL_NAME)); + if (qualifiers.isEmpty()) { + qualifiers.add(AnnotationInstance.builder(Default.class).build()); + } + } + + static java.lang.reflect.Type getFirstActualTypeArgument(java.lang.reflect.Type requiredType) { if (requiredType instanceof ParameterizedType) { final ParameterizedType parameterizedType = (ParameterizedType) requiredType; - if (List.class.equals(parameterizedType.getRawType())) { - // List -> String - return parameterizedType.getActualTypeArguments()[0]; - } + // List -> String + return parameterizedType.getActualTypeArguments()[0]; } return null; } @@ -1203,6 +1247,25 @@ private static boolean isTypeArgumentInstanceHandle(java.lang.reflect.Type type) return false; } + private boolean injectionPointMatchesBean(java.lang.reflect.Type injectionPointType, AnnotatedElement annotatedElement, + List allQualifiers, BeanResolver beanResolver, BeanInfo bean) { + Type requiredType; + Set requiredQualifiers = getQualifiers(annotatedElement, allQualifiers); + if (isListAllInjectionPoint(injectionPointType, + Arrays.stream(annotatedElement.getAnnotations()) + .filter(a -> allQualifiers.contains(DotName.createSimple(a.annotationType()))) + .toArray(Annotation[]::new), + annotatedElement)) { + requiredType = Types.jandexType(getFirstActualTypeArgument(injectionPointType)); + adaptListAllQualifiers(requiredQualifiers); + } else if (Instance.class.isAssignableFrom(QuarkusComponentTestConfiguration.getRawType(injectionPointType))) { + requiredType = Types.jandexType(getFirstActualTypeArgument(injectionPointType)); + } else { + requiredType = Types.jandexType(injectionPointType); + } + return beanResolver.matches(bean, requiredType, requiredQualifiers); + } + private File getTestOutputDirectory(Class testClass) { String outputDirectory = System.getProperty(QUARKUS_TEST_COMPONENT_OUTPUT_DIRECTORY); File testOutputDirectory; diff --git a/test-framework/junit5-component/src/main/java/io/quarkus/test/component/QuarkusComponentTestExtensionBuilder.java b/test-framework/junit5-component/src/main/java/io/quarkus/test/component/QuarkusComponentTestExtensionBuilder.java index b1fdccb0d1aeb..229ec3d6555b2 100644 --- a/test-framework/junit5-component/src/main/java/io/quarkus/test/component/QuarkusComponentTestExtensionBuilder.java +++ b/test-framework/junit5-component/src/main/java/io/quarkus/test/component/QuarkusComponentTestExtensionBuilder.java @@ -3,8 +3,10 @@ import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.function.Consumer; import java.util.function.Function; @@ -27,7 +29,7 @@ public class QuarkusComponentTestExtensionBuilder { public static final int DEFAULT_CONFIG_SOURCE_ORDINAL = 500; private final Map configProperties = new HashMap<>(); - private final List> componentClasses = new ArrayList<>(); + private final Set> componentClasses = new HashSet<>(); private final List> mockConfigurators = new ArrayList<>(); private final List annotationsTransformers = new ArrayList<>(); private final List> configConverters = new ArrayList<>(); @@ -164,7 +166,7 @@ public QuarkusComponentTestExtension build() { converters = List.copyOf(converters); } return new QuarkusComponentTestExtension(new QuarkusComponentTestConfiguration(Map.copyOf(configProperties), - List.copyOf(componentClasses), List.copyOf(mockConfigurators), useDefaultConfigProperties, + Set.copyOf(componentClasses), List.copyOf(mockConfigurators), useDefaultConfigProperties, addNestedClassesAsComponents, configSourceOrdinal, List.copyOf(annotationsTransformers), converters, configBuilderCustomizer)); } diff --git a/test-framework/junit5-component/src/test/java/io/quarkus/test/component/declarative/InstanceComponentTest.java b/test-framework/junit5-component/src/test/java/io/quarkus/test/component/declarative/InstanceComponentTest.java new file mode 100644 index 0000000000000..96e2a3fbf4625 --- /dev/null +++ b/test-framework/junit5-component/src/test/java/io/quarkus/test/component/declarative/InstanceComponentTest.java @@ -0,0 +1,23 @@ +package io.quarkus.test.component.declarative; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +import jakarta.enterprise.inject.Instance; +import jakarta.inject.Inject; + +import org.junit.jupiter.api.Test; + +import io.quarkus.test.component.QuarkusComponentTest; + +@QuarkusComponentTest +public class InstanceComponentTest { + + @Inject + Instance instance; + + @Test + public void testComponents() { + assertTrue(instance.get().ping()); + } + +} diff --git a/test-framework/junit5-component/src/test/java/io/quarkus/test/component/declarative/InstanceInterfaceTest.java b/test-framework/junit5-component/src/test/java/io/quarkus/test/component/declarative/InstanceInterfaceTest.java new file mode 100644 index 0000000000000..65be082614f74 --- /dev/null +++ b/test-framework/junit5-component/src/test/java/io/quarkus/test/component/declarative/InstanceInterfaceTest.java @@ -0,0 +1,24 @@ +package io.quarkus.test.component.declarative; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +import jakarta.enterprise.inject.Instance; +import jakarta.inject.Inject; + +import org.junit.jupiter.api.Test; + +import io.quarkus.test.component.QuarkusComponentTest; + +@QuarkusComponentTest +public class InstanceInterfaceTest { + + @Inject + Instance instance; + + @Test + public void testComponents() { + // SomeInterface is registered as component but no implementation is added automatically + assertTrue(instance.isUnsatisfied()); + } + +} diff --git a/test-framework/junit5-component/src/test/java/io/quarkus/test/component/declarative/ListAllComponentTest.java b/test-framework/junit5-component/src/test/java/io/quarkus/test/component/declarative/ListAllComponentTest.java new file mode 100644 index 0000000000000..259c14683eeb6 --- /dev/null +++ b/test-framework/junit5-component/src/test/java/io/quarkus/test/component/declarative/ListAllComponentTest.java @@ -0,0 +1,27 @@ +package io.quarkus.test.component.declarative; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.List; + +import jakarta.inject.Inject; + +import org.junit.jupiter.api.Test; + +import io.quarkus.arc.All; +import io.quarkus.test.component.QuarkusComponentTest; + +@QuarkusComponentTest +public class ListAllComponentTest { + + @Inject + @All + List components; + + @Test + public void testComponents() { + // SomeBean is registered as component + assertEquals(1, components.size()); + } + +} diff --git a/test-framework/junit5-component/src/test/java/io/quarkus/test/component/declarative/ListAllInterfaceTest.java b/test-framework/junit5-component/src/test/java/io/quarkus/test/component/declarative/ListAllInterfaceTest.java new file mode 100644 index 0000000000000..f81226bc526cc --- /dev/null +++ b/test-framework/junit5-component/src/test/java/io/quarkus/test/component/declarative/ListAllInterfaceTest.java @@ -0,0 +1,27 @@ +package io.quarkus.test.component.declarative; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.List; + +import jakarta.inject.Inject; + +import org.junit.jupiter.api.Test; + +import io.quarkus.arc.All; +import io.quarkus.test.component.QuarkusComponentTest; + +@QuarkusComponentTest +public class ListAllInterfaceTest { + + @Inject + @All + List components; + + @Test + public void testComponents() { + // SomeInterface is registered as component but no implementation is added automatically + assertEquals(0, components.size()); + } + +} diff --git a/test-framework/junit5-component/src/test/java/io/quarkus/test/component/declarative/ListAllMockTest.java b/test-framework/junit5-component/src/test/java/io/quarkus/test/component/declarative/ListAllMockTest.java index 8e9ca9f4b1456..5534cc64c707a 100644 --- a/test-framework/junit5-component/src/test/java/io/quarkus/test/component/declarative/ListAllMockTest.java +++ b/test-framework/junit5-component/src/test/java/io/quarkus/test/component/declarative/ListAllMockTest.java @@ -27,10 +27,6 @@ public class ListAllMockTest { @InjectMock Delta delta; - @Inject - @All - List deltas; - @InjectMock @SimpleQualifier Bravo bravo; @@ -42,7 +38,6 @@ public void testMock() { assertFalse(component.ping()); assertEquals(1, component.bravos.size()); assertEquals("ok", component.bravos.get(0).ping()); - assertEquals(deltas.get(0).ping(), component.ping()); } @Singleton diff --git a/test-framework/junit5-component/src/test/java/io/quarkus/test/component/declarative/SomeBean.java b/test-framework/junit5-component/src/test/java/io/quarkus/test/component/declarative/SomeBean.java new file mode 100644 index 0000000000000..ee40502137aff --- /dev/null +++ b/test-framework/junit5-component/src/test/java/io/quarkus/test/component/declarative/SomeBean.java @@ -0,0 +1,20 @@ +package io.quarkus.test.component.declarative; + +import jakarta.annotation.PostConstruct; +import jakarta.enterprise.context.ApplicationScoped; + +@ApplicationScoped +public class SomeBean implements SomeInterface { + + private boolean val; + + public boolean ping() { + return val; + } + + @PostConstruct + void init() { + val = true; + } + +} diff --git a/test-framework/junit5-component/src/test/java/io/quarkus/test/component/declarative/SomeInterface.java b/test-framework/junit5-component/src/test/java/io/quarkus/test/component/declarative/SomeInterface.java new file mode 100644 index 0000000000000..b3801654619c4 --- /dev/null +++ b/test-framework/junit5-component/src/test/java/io/quarkus/test/component/declarative/SomeInterface.java @@ -0,0 +1,5 @@ +package io.quarkus.test.component.declarative; + +public interface SomeInterface { + +} diff --git a/test-framework/junit5-component/src/test/java/io/quarkus/test/component/paraminject/ParameterInjectionTest.java b/test-framework/junit5-component/src/test/java/io/quarkus/test/component/paraminject/ParameterInjectionTest.java index 8e81c48b72c00..4825baa7c1436 100644 --- a/test-framework/junit5-component/src/test/java/io/quarkus/test/component/paraminject/ParameterInjectionTest.java +++ b/test-framework/junit5-component/src/test/java/io/quarkus/test/component/paraminject/ParameterInjectionTest.java @@ -3,10 +3,13 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.List; import java.util.function.Supplier; +import jakarta.enterprise.inject.Instance; + import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInfo; import org.junit.jupiter.api.extension.ExtendWith; @@ -43,7 +46,8 @@ public void testParamsInjection( // And so no matching bean exists @SkipInject Supplier shouldBeTrue, // @All List<> needs special handling - @All List allMyComponents) { + @All List allMyComponents, + Instance instance) { Mockito.when(charlie.ping()).thenReturn("foo"); assertNotNull(testInfo); assertEquals("foo and BAZ", myComponent.ping()); @@ -51,6 +55,8 @@ public void testParamsInjection( assertEquals(1, allMyComponents.size()); assertEquals(myComponent.ping(), allMyComponents.get(0).ping()); assertEquals(Boolean.TRUE, shouldBeTrue.get()); + assertNotNull(instance); + assertTrue(instance.isResolvable()); } public static class MyParamResolver implements ParameterResolver {