diff --git a/common/taskana-common/src/main/java/pro/taskana/common/internal/util/ReflectionUtil.java b/common/taskana-common/src/main/java/pro/taskana/common/internal/util/ReflectionUtil.java index 2559fd8bd..3bf3cbf7c 100644 --- a/common/taskana-common/src/main/java/pro/taskana/common/internal/util/ReflectionUtil.java +++ b/common/taskana-common/src/main/java/pro/taskana/common/internal/util/ReflectionUtil.java @@ -44,4 +44,18 @@ public class ReflectionUtil { public static Class wrap(Class c) { return c.isPrimitive() ? (Class) PRIMITIVES_TO_WRAPPERS.get(c) : c; } + + public static Object getEnclosingInstance(Object instance) { + return Arrays.stream(instance.getClass().getDeclaredFields()) + .filter(Field::isSynthetic) + .filter(f -> f.getName().startsWith("this")) + .findFirst() + .map( + CheckedFunction.wrap( + field -> { + field.setAccessible(true); + return field.get(instance); + })) + .orElse(null); + } } diff --git a/common/taskana-common/src/test/java/pro/taskana/common/internal/util/ReflectionUtilTest.java b/common/taskana-common/src/test/java/pro/taskana/common/internal/util/ReflectionUtilTest.java index 9798103b7..2a8636811 100644 --- a/common/taskana-common/src/test/java/pro/taskana/common/internal/util/ReflectionUtilTest.java +++ b/common/taskana-common/src/test/java/pro/taskana/common/internal/util/ReflectionUtilTest.java @@ -6,6 +6,9 @@ import java.lang.reflect.Field; import java.util.List; import org.junit.jupiter.api.Test; +import pro.taskana.common.internal.util.TopLevelTestClass.FirstNestedClass; +import pro.taskana.common.internal.util.TopLevelTestClass.FirstNestedClass.SecondNestedClass; + class ReflectionUtilTest { @Test @@ -31,6 +34,36 @@ class ReflectionUtilTest { assertThat(wrap).isEqualTo(TestClass.class); } + @Test + void should_ReturnNull_For_TopLevelClass() { + TopLevelTestClass topLevelTestClass = new TopLevelTestClass(); + + Object enclosingInstance = ReflectionUtil.getEnclosingInstance(topLevelTestClass); + + assertThat(enclosingInstance).isNull(); + } + + @Test + void should_ReturnTopLevelInstance_For_NestedInstance() { + TopLevelTestClass topLevelTestClass = new TopLevelTestClass(); + FirstNestedClass firstNestedClass = topLevelTestClass.new FirstNestedClass(); + + Object enclosingInstance = ReflectionUtil.getEnclosingInstance(firstNestedClass); + + assertThat(enclosingInstance).isSameAs(topLevelTestClass); + } + + @Test + void should_ReturnNestedInstance_For_NestedNestedInstance() { + TopLevelTestClass topLevelTestClass = new TopLevelTestClass(); + FirstNestedClass firstNestedClass = topLevelTestClass.new FirstNestedClass(); + SecondNestedClass secondNestedClass = firstNestedClass.new SecondNestedClass(); + + Object enclosingInstance = ReflectionUtil.getEnclosingInstance(secondNestedClass); + + assertThat(enclosingInstance).isSameAs(firstNestedClass); + } + static class TestClass { @SuppressWarnings("unused") String fieldA; @@ -46,3 +79,16 @@ class ReflectionUtilTest { String fieldC; } } + +@SuppressWarnings({"checkstyle:OneTopLevelClass", "InnerClassMayBeStatic", "unused"}) +class TopLevelTestClass { + String someField; + + class FirstNestedClass { + String someField; + + class SecondNestedClass { + String someField; + } + } +} diff --git a/lib/taskana-core-test/src/test/java/acceptance/taskrouting/TaskRoutingAccTest.java b/lib/taskana-core-test/src/test/java/acceptance/taskrouting/TaskRoutingAccTest.java index 19dcc8258..7b91a1e0b 100644 --- a/lib/taskana-core-test/src/test/java/acceptance/taskrouting/TaskRoutingAccTest.java +++ b/lib/taskana-core-test/src/test/java/acceptance/taskrouting/TaskRoutingAccTest.java @@ -55,8 +55,6 @@ class TaskRoutingAccTest { .permission(WorkbasketPermission.READ) .permission(WorkbasketPermission.APPEND) .buildAndStore(workbasketService); - - TaskRoutingProviderForDomainA.domainAWorkbasketId = domainAWorkbasket.getId(); } @WithAccessId(user = "user-1-1") @@ -83,9 +81,7 @@ class TaskRoutingAccTest { assertThat(createdTask.getWorkbasketSummary()).isEqualTo(domainAWorkbasket); } - public static class TaskRoutingProviderForDomainA implements TaskRoutingProvider { - - static String domainAWorkbasketId; + class TaskRoutingProviderForDomainA implements TaskRoutingProvider { @Override public void initialize(TaskanaEngine taskanaEngine) {} @@ -93,7 +89,7 @@ class TaskRoutingAccTest { @Override public String determineWorkbasketId(Task task) { if ("DOMAIN_A".equals(task.getDomain())) { - return domainAWorkbasketId; + return domainAWorkbasket.getId(); } return null; } diff --git a/lib/taskana-test-api/src/main/java/pro/taskana/testapi/extensions/TaskanaInitializationExtension.java b/lib/taskana-test-api/src/main/java/pro/taskana/testapi/extensions/TaskanaInitializationExtension.java index 7fdfe894c..dbf30cca2 100644 --- a/lib/taskana-test-api/src/main/java/pro/taskana/testapi/extensions/TaskanaInitializationExtension.java +++ b/lib/taskana-test-api/src/main/java/pro/taskana/testapi/extensions/TaskanaInitializationExtension.java @@ -4,6 +4,7 @@ import static org.junit.platform.commons.support.AnnotationSupport.isAnnotated; import static pro.taskana.testapi.util.ExtensionCommunicator.getClassLevelStore; import static pro.taskana.testapi.util.ExtensionCommunicator.isTopLevelClass; +import java.util.HashMap; import java.util.Map; import javax.sql.DataSource; import org.apache.ibatis.session.SqlSession; @@ -29,6 +30,7 @@ import pro.taskana.common.internal.InternalTaskanaEngine; import pro.taskana.common.internal.JobServiceImpl; import pro.taskana.common.internal.TaskanaEngineImpl; import pro.taskana.common.internal.security.CurrentUserContextImpl; +import pro.taskana.common.internal.util.ReflectionUtil; import pro.taskana.common.internal.util.SpiLoader; import pro.taskana.monitor.api.MonitorService; import pro.taskana.monitor.internal.MonitorServiceImpl; @@ -68,7 +70,8 @@ public class TaskanaInitializationExtension implements TestInstancePostProcessor TaskanaEngine taskanaEngine; try (MockedStatic staticMock = Mockito.mockStatic(SpiLoader.class)) { - ServiceProviderExtractor.extractServiceProviders(testClass) + ServiceProviderExtractor.extractServiceProviders( + testClass, extractEnclosingTestInstances(testInstance)) .forEach( (spi, serviceProviders) -> staticMock.when(() -> SpiLoader.load(spi)).thenReturn(serviceProviders)); @@ -80,6 +83,15 @@ public class TaskanaInitializationExtension implements TestInstancePostProcessor } } + private static Map, Object> extractEnclosingTestInstances(Object instance) { + HashMap, Object> instanceByClass = new HashMap<>(); + while (instance != null) { + instanceByClass.put(instance.getClass(), instance); + instance = ReflectionUtil.getEnclosingInstance(instance); + } + return instanceByClass; + } + private static TaskanaEngineConfiguration createDefaultTaskanaEngineConfiguration(Store store) { String schemaName = store.get(TestContainerExtension.STORE_SCHEMA_NAME, String.class); if (schemaName == null) { diff --git a/lib/taskana-test-api/src/main/java/pro/taskana/testapi/util/ServiceProviderExtractor.java b/lib/taskana-test-api/src/main/java/pro/taskana/testapi/util/ServiceProviderExtractor.java index 63433fa51..e801102f6 100644 --- a/lib/taskana-test-api/src/main/java/pro/taskana/testapi/util/ServiceProviderExtractor.java +++ b/lib/taskana-test-api/src/main/java/pro/taskana/testapi/util/ServiceProviderExtractor.java @@ -1,9 +1,7 @@ package pro.taskana.testapi.util; import static org.junit.platform.commons.support.AnnotationSupport.findRepeatableAnnotations; -import static pro.taskana.common.internal.util.CheckedFunction.wrap; -import java.lang.reflect.Constructor; import java.lang.reflect.Modifier; import java.util.Arrays; import java.util.List; @@ -12,6 +10,7 @@ import java.util.Map.Entry; import java.util.Set; import java.util.stream.Collectors; import org.junit.platform.commons.JUnitException; +import org.junit.platform.commons.support.ReflectionSupport; import pro.taskana.spi.history.api.TaskanaHistory; import pro.taskana.spi.priority.api.PriorityServiceProvider; @@ -36,7 +35,8 @@ public class ServiceProviderExtractor { throw new IllegalStateException("utility class"); } - public static Map, List> extractServiceProviders(Class testClass) { + public static Map, List> extractServiceProviders( + Class testClass, Map, Object> enclosingTestInstancesByClass) { List withServiceProviders = findRepeatableAnnotations(testClass, WithServiceProvider.class); @@ -44,7 +44,9 @@ public class ServiceProviderExtractor { .peek(entry -> validateServiceProviders(entry.getKey(), entry.getValue())) .collect( Collectors.toMap( - Entry::getKey, entry -> instantiateServiceProviders(entry.getValue()))); + Entry::getKey, + entry -> + instantiateServiceProviders(entry.getValue(), enclosingTestInstancesByClass))); } private static void validateServiceProviders(Class spi, List> serviceProviders) { @@ -69,24 +71,33 @@ public class ServiceProviderExtractor { Collectors.toList()))); } - private static List instantiateServiceProviders(List> serviceProviders) { + private static List instantiateServiceProviders( + List> serviceProviders, Map, Object> enclosingTestInstancesByClass) { return serviceProviders.stream() - .map(wrap(ServiceProviderExtractor::instantiateClass)) + .map(clz -> instantiateClass(clz, enclosingTestInstancesByClass)) .collect(Collectors.toList()); } - private static Object instantiateClass(Class clz) throws Exception { + private static Object instantiateClass( + Class clz, Map, Object> enclosingTestInstancesByClass) { // we don't have to consider anonymous classes since they can't be passed as an argument to // the WithServiceProvider annotation. if (clz.isLocalClass() || (clz.isMemberClass() && !Modifier.isStatic(clz.getModifiers()))) { - Class motherClass = clz.getEnclosingClass(); - Object motherInstance = instantiateClass(motherClass); - Constructor constructor = clz.getDeclaredConstructor(motherClass); - constructor.setAccessible(true); - return constructor.newInstance(motherInstance); + try { + Class motherClass = clz.getEnclosingClass(); + Object motherInstance = + enclosingTestInstancesByClass.getOrDefault( + motherClass, instantiateClass(motherClass, enclosingTestInstancesByClass)); + return ReflectionSupport.newInstance(clz, motherInstance); + } catch (Exception e) { + //noinspection ConstantConditions + if (NoSuchMethodException.class == e.getClass()) { + throw new JUnitException( + "test-api does not support local class which accesses method variables"); + } + throw e; + } } - Constructor constructor = clz.getDeclaredConstructor(); - constructor.setAccessible(true); - return constructor.newInstance(); + return ReflectionSupport.newInstance(clz); } } diff --git a/lib/taskana-test-api/src/test/java/pro/taskana/testapi/util/ServiceProviderExtractorTest.java b/lib/taskana-test-api/src/test/java/pro/taskana/testapi/util/ServiceProviderExtractorTest.java index 4556a5cc3..17af43d9f 100644 --- a/lib/taskana-test-api/src/test/java/pro/taskana/testapi/util/ServiceProviderExtractorTest.java +++ b/lib/taskana-test-api/src/test/java/pro/taskana/testapi/util/ServiceProviderExtractorTest.java @@ -1,5 +1,6 @@ package pro.taskana.testapi.util; +import static java.util.Map.entry; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static pro.taskana.testapi.util.ServiceProviderExtractor.extractServiceProviders; @@ -14,6 +15,7 @@ import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.TestInstance.Lifecycle; import org.junit.platform.commons.JUnitException; +import pro.taskana.common.internal.util.ReflectionUtil; import pro.taskana.spi.priority.api.PriorityServiceProvider; import pro.taskana.spi.task.api.CreateTaskPreprocessor; import pro.taskana.task.api.models.Task; @@ -66,16 +68,30 @@ class ServiceProviderExtractorTest { } } + @SuppressWarnings("InnerClassMayBeStatic") + class NonStaticCreateTaskPreprocessorOutsideOfTestClass implements CreateTaskPreprocessor { + @Override + public void processTaskBeforeCreation(Task taskToProcess) { + // implementation not important for the tests + } + } + @Nested @TestInstance(Lifecycle.PER_CLASS) class ServiceProviderInstantiation { + private final Map, Object> enclosingTestInstancesByClass = + Map.ofEntries( + entry(ServiceProviderInstantiation.class, this), + entry(ServiceProviderExtractorTest.class, ServiceProviderExtractorTest.this)); + @Test void should_ReturnEmptyMap_When_NoServiceProviderIsDefined() { - class ExampleClazzWithNoServiceProviders {} + class ExampleTestClassWithNoServiceProviders {} Map, List> extractServiceProviders = - extractServiceProviders(ExampleClazzWithNoServiceProviders.class); + extractServiceProviders( + ExampleTestClassWithNoServiceProviders.class, enclosingTestInstancesByClass); assertThat(extractServiceProviders).isEmpty(); } @@ -85,16 +101,17 @@ class ServiceProviderExtractorTest { @WithServiceProvider( serviceProviderInterface = CreateTaskPreprocessor.class, serviceProviders = TopLevelCreateTaskPreprocessor.class) - class ExampleClassWithServiceProviders {} + class ExampleTestClassWithServiceProviders {} Map, List> extractServiceProviders = - extractServiceProviders(ExampleClassWithServiceProviders.class); + extractServiceProviders( + ExampleTestClassWithServiceProviders.class, enclosingTestInstancesByClass); assertThat(extractServiceProviders).containsOnlyKeys(CreateTaskPreprocessor.class); assertThat(extractServiceProviders.get(CreateTaskPreprocessor.class)) - .extracting(Object::getClass) - .asList() - .containsExactly(TopLevelCreateTaskPreprocessor.class); + .hasExactlyElementsOfTypes(TopLevelCreateTaskPreprocessor.class) + .extracting(ReflectionUtil::getEnclosingInstance) + .containsOnlyNulls(); } @Test @@ -102,16 +119,17 @@ class ServiceProviderExtractorTest { @WithServiceProvider( serviceProviderInterface = CreateTaskPreprocessor.class, serviceProviders = StaticCreateTaskPreprocessor.class) - class ExampleClassWithServiceProviders {} + class ExampleTestClassWithServiceProviders {} Map, List> extractServiceProviders = - extractServiceProviders(ExampleClassWithServiceProviders.class); + extractServiceProviders( + ExampleTestClassWithServiceProviders.class, enclosingTestInstancesByClass); assertThat(extractServiceProviders).containsOnlyKeys(CreateTaskPreprocessor.class); assertThat(extractServiceProviders.get(CreateTaskPreprocessor.class)) - .extracting(Object::getClass) - .asList() - .containsExactly(StaticCreateTaskPreprocessor.class); + .hasExactlyElementsOfTypes(StaticCreateTaskPreprocessor.class) + .extracting(ReflectionUtil::getEnclosingInstance) + .containsOnlyNulls(); } @Test @@ -119,16 +137,17 @@ class ServiceProviderExtractorTest { @WithServiceProvider( serviceProviderInterface = CreateTaskPreprocessor.class, serviceProviders = PrivateStaticCreateTaskPreprocessor.class) - class ExampleClassWithServiceProviders {} + class ExampleTestClassWithServiceProviders {} Map, List> extractServiceProviders = - extractServiceProviders(ExampleClassWithServiceProviders.class); + extractServiceProviders( + ExampleTestClassWithServiceProviders.class, enclosingTestInstancesByClass); assertThat(extractServiceProviders).containsOnlyKeys(CreateTaskPreprocessor.class); assertThat(extractServiceProviders.get(CreateTaskPreprocessor.class)) - .extracting(Object::getClass) - .asList() - .containsExactly(PrivateStaticCreateTaskPreprocessor.class); + .hasExactlyElementsOfTypes(PrivateStaticCreateTaskPreprocessor.class) + .extracting(ReflectionUtil::getEnclosingInstance) + .containsOnlyNulls(); } @Test @@ -143,33 +162,35 @@ class ServiceProviderExtractorTest { @WithServiceProvider( serviceProviderInterface = CreateTaskPreprocessor.class, serviceProviders = LocalCreateTaskPreprocessor.class) - class ExampleClassWithServiceProviders {} + class ExampleTestClassWithServiceProviders {} Map, List> extractServiceProviders = - extractServiceProviders(ExampleClassWithServiceProviders.class); + extractServiceProviders( + ExampleTestClassWithServiceProviders.class, enclosingTestInstancesByClass); assertThat(extractServiceProviders).containsOnlyKeys(CreateTaskPreprocessor.class); assertThat(extractServiceProviders.get(CreateTaskPreprocessor.class)) - .extracting(Object::getClass) - .asList() - .containsExactly(LocalCreateTaskPreprocessor.class); + .hasExactlyElementsOfTypes(LocalCreateTaskPreprocessor.class) + .extracting(ReflectionUtil::getEnclosingInstance) + .containsExactly(this); } @Test void should_InstantiateServiceProvider_When_ServiceProviderIsNonStaticMemberClass() { @WithServiceProvider( serviceProviderInterface = CreateTaskPreprocessor.class, - serviceProviders = NonStaticCreateTaskPreprocessor.class) - class ExampleClassWithServiceProviders {} + serviceProviders = NonStaticCreateTaskPreprocessorInsideOfTestClass.class) + class ExampleTestClassWithServiceProviders {} Map, List> extractServiceProviders = - extractServiceProviders(ExampleClassWithServiceProviders.class); + extractServiceProviders( + ExampleTestClassWithServiceProviders.class, enclosingTestInstancesByClass); assertThat(extractServiceProviders).containsOnlyKeys(CreateTaskPreprocessor.class); assertThat(extractServiceProviders.get(CreateTaskPreprocessor.class)) - .extracting(Object::getClass) - .asList() - .containsExactly(NonStaticCreateTaskPreprocessor.class); + .hasExactlyElementsOfTypes(NonStaticCreateTaskPreprocessorInsideOfTestClass.class) + .extracting(ReflectionUtil::getEnclosingInstance) + .containsExactly(this); } @Test @@ -177,20 +198,38 @@ class ServiceProviderExtractorTest { @WithServiceProvider( serviceProviderInterface = CreateTaskPreprocessor.class, serviceProviders = PrivateNonStaticCreateTaskPreprocessor.class) - class ExampleClassWithServiceProviders {} + class ExampleTestClassWithServiceProviders {} Map, List> extractServiceProviders = - extractServiceProviders(ExampleClassWithServiceProviders.class); + extractServiceProviders( + ExampleTestClassWithServiceProviders.class, enclosingTestInstancesByClass); assertThat(extractServiceProviders).containsOnlyKeys(CreateTaskPreprocessor.class); assertThat(extractServiceProviders.get(CreateTaskPreprocessor.class)) - .extracting(Object::getClass) - .asList() - .containsExactly(PrivateNonStaticCreateTaskPreprocessor.class); + .hasExactlyElementsOfTypes(PrivateNonStaticCreateTaskPreprocessor.class) + .extracting(ReflectionUtil::getEnclosingInstance) + .containsExactly(this); } - class NonStaticCreateTaskPreprocessor implements CreateTaskPreprocessor { + @Test + void should_InstantiateServiceProvider_When_ItIsNonStaticMemberClassOutsideOfTestClass() { + @WithServiceProvider( + serviceProviderInterface = CreateTaskPreprocessor.class, + serviceProviders = NonStaticCreateTaskPreprocessorOutsideOfTestClass.class) + class ExampleTestClassWithServiceProviders {} + Map, List> extractServiceProviders = + extractServiceProviders( + ExampleTestClassWithServiceProviders.class, enclosingTestInstancesByClass); + + assertThat(extractServiceProviders).containsOnlyKeys(CreateTaskPreprocessor.class); + assertThat(extractServiceProviders.get(CreateTaskPreprocessor.class)) + .hasExactlyElementsOfTypes(NonStaticCreateTaskPreprocessorOutsideOfTestClass.class) + .extracting(ReflectionUtil::getEnclosingInstance) + .containsExactly(ServiceProviderExtractorTest.this); + } + + class NonStaticCreateTaskPreprocessorInsideOfTestClass implements CreateTaskPreprocessor { @Override public void processTaskBeforeCreation(Task taskToProcess) { // implementation not important for the tests @@ -210,21 +249,26 @@ class ServiceProviderExtractorTest { @TestInstance(Lifecycle.PER_CLASS) class ExtractServiceProvidersFromSingleServiceProviderInterface { + private final Map, Object> enclosingTestInstancesByClass = + Map.ofEntries( + entry(ExtractServiceProvidersFromSingleServiceProviderInterface.class, this), + entry(ServiceProviderExtractorTest.class, ServiceProviderExtractorTest.this)); + @Test void should_ExtractServiceProvider() { @WithServiceProvider( serviceProviderInterface = CreateTaskPreprocessor.class, serviceProviders = DummyTaskPreprocessor1.class) - class ExampleClassWithServiceProviders {} + class ExampleTestClassWithServiceProviders {} Map, List> extractServiceProviders = - extractServiceProviders(ExampleClassWithServiceProviders.class); + extractServiceProviders( + ExampleTestClassWithServiceProviders.class, enclosingTestInstancesByClass); assertThat(extractServiceProviders).containsOnlyKeys(CreateTaskPreprocessor.class); assertThat(extractServiceProviders.get(CreateTaskPreprocessor.class)) - .extracting(Object::getClass) .asList() - .containsExactly(DummyTaskPreprocessor1.class); + .hasExactlyElementsOfTypes(DummyTaskPreprocessor1.class); } @Test @@ -232,16 +276,16 @@ class ServiceProviderExtractorTest { @WithServiceProvider( serviceProviderInterface = CreateTaskPreprocessor.class, serviceProviders = {DummyTaskPreprocessor1.class, DummyTaskPreprocessor2.class}) - class ExampleClassWithServiceProviders {} + class ExampleTestClassWithServiceProviders {} Map, List> extractServiceProviders = - extractServiceProviders(ExampleClassWithServiceProviders.class); + extractServiceProviders( + ExampleTestClassWithServiceProviders.class, enclosingTestInstancesByClass); assertThat(extractServiceProviders).containsOnlyKeys(CreateTaskPreprocessor.class); assertThat(extractServiceProviders.get(CreateTaskPreprocessor.class)) - .extracting(Object::getClass) .asList() - .containsExactly(DummyTaskPreprocessor1.class, DummyTaskPreprocessor2.class); + .hasExactlyElementsOfTypes(DummyTaskPreprocessor1.class, DummyTaskPreprocessor2.class); } @Test @@ -252,16 +296,15 @@ class ServiceProviderExtractorTest { @WithServiceProvider( serviceProviderInterface = CreateTaskPreprocessor.class, serviceProviders = DummyTaskPreprocessor2.class) - class ExampleClassWithServiceProviders {} + class ExampleTestClassWithServiceProviders {} Map, List> extractServiceProviders = - extractServiceProviders(ExampleClassWithServiceProviders.class); + extractServiceProviders( + ExampleTestClassWithServiceProviders.class, enclosingTestInstancesByClass); assertThat(extractServiceProviders).containsOnlyKeys(CreateTaskPreprocessor.class); assertThat(extractServiceProviders.get(CreateTaskPreprocessor.class)) - .extracting(Object::getClass) - .asList() - .containsExactly(DummyTaskPreprocessor1.class, DummyTaskPreprocessor2.class); + .hasExactlyElementsOfTypes(DummyTaskPreprocessor1.class, DummyTaskPreprocessor2.class); } @Test @@ -269,16 +312,15 @@ class ServiceProviderExtractorTest { @WithServiceProvider( serviceProviderInterface = CreateTaskPreprocessor.class, serviceProviders = {DummyTaskPreprocessor1.class, DummyTaskPreprocessor1.class}) - class ExampleClassWithServiceProviders {} + class ExampleTestClassWithServiceProviders {} Map, List> extractServiceProviders = - extractServiceProviders(ExampleClassWithServiceProviders.class); + extractServiceProviders( + ExampleTestClassWithServiceProviders.class, enclosingTestInstancesByClass); assertThat(extractServiceProviders).containsOnlyKeys(CreateTaskPreprocessor.class); assertThat(extractServiceProviders.get(CreateTaskPreprocessor.class)) - .extracting(Object::getClass) - .asList() - .containsExactly(DummyTaskPreprocessor1.class, DummyTaskPreprocessor1.class); + .hasExactlyElementsOfTypes(DummyTaskPreprocessor1.class, DummyTaskPreprocessor1.class); } } @@ -286,6 +328,11 @@ class ServiceProviderExtractorTest { @TestInstance(Lifecycle.PER_CLASS) class ExtractMultipleServiceProvidersFromMultipleServiceProviderInterfaces { + private final Map, Object> enclosingTestInstancesByClass = + Map.ofEntries( + entry(ExtractMultipleServiceProvidersFromMultipleServiceProviderInterfaces.class, this), + entry(ServiceProviderExtractorTest.class, ServiceProviderExtractorTest.this)); + @Test void should_ExtractServiceProviders() { @WithServiceProvider( @@ -294,21 +341,18 @@ class ServiceProviderExtractorTest { @WithServiceProvider( serviceProviderInterface = PriorityServiceProvider.class, serviceProviders = DummyPriorityServiceProvider1.class) - class ExampleClassWithServiceProviders {} + class ExampleTestClassWithServiceProviders {} Map, List> extractServiceProviders = - extractServiceProviders(ExampleClassWithServiceProviders.class); + extractServiceProviders( + ExampleTestClassWithServiceProviders.class, enclosingTestInstancesByClass); assertThat(extractServiceProviders) .containsOnlyKeys(CreateTaskPreprocessor.class, PriorityServiceProvider.class); assertThat(extractServiceProviders.get(CreateTaskPreprocessor.class)) - .extracting(Object::getClass) - .asList() - .containsExactly(DummyTaskPreprocessor1.class); + .hasExactlyElementsOfTypes(DummyTaskPreprocessor1.class); assertThat(extractServiceProviders.get(PriorityServiceProvider.class)) - .extracting(Object::getClass) - .asList() - .containsExactly(DummyPriorityServiceProvider1.class); + .hasExactlyElementsOfTypes(DummyPriorityServiceProvider1.class); } @Test @@ -322,21 +366,18 @@ class ServiceProviderExtractorTest { DummyPriorityServiceProvider1.class, DummyPriorityServiceProvider2.class }) - class ExampleClassWithServiceProviders {} + class ExampleTestClassWithServiceProviders {} Map, List> extractServiceProviders = - extractServiceProviders(ExampleClassWithServiceProviders.class); + extractServiceProviders( + ExampleTestClassWithServiceProviders.class, enclosingTestInstancesByClass); assertThat(extractServiceProviders) .containsOnlyKeys(CreateTaskPreprocessor.class, PriorityServiceProvider.class); assertThat(extractServiceProviders.get(CreateTaskPreprocessor.class)) - .extracting(Object::getClass) - .asList() - .containsExactly(DummyTaskPreprocessor1.class, DummyTaskPreprocessor2.class); + .hasExactlyElementsOfTypes(DummyTaskPreprocessor1.class, DummyTaskPreprocessor2.class); assertThat(extractServiceProviders.get(PriorityServiceProvider.class)) - .extracting(Object::getClass) - .asList() - .containsExactly( + .hasExactlyElementsOfTypes( DummyPriorityServiceProvider1.class, DummyPriorityServiceProvider2.class); } } @@ -350,9 +391,10 @@ class ServiceProviderExtractorTest { @WithServiceProvider( serviceProviderInterface = ErrorHandling.class, serviceProviders = DummyTaskPreprocessor1.class) - class ExampleClassWithServiceProviders {} + class ExampleTestClassWithServiceProviders {} - ThrowingCallable call = () -> extractServiceProviders(ExampleClassWithServiceProviders.class); + ThrowingCallable call = + () -> extractServiceProviders(ExampleTestClassWithServiceProviders.class, Map.of()); assertThatThrownBy(call) .isInstanceOf(JUnitException.class) @@ -364,9 +406,10 @@ class ServiceProviderExtractorTest { @WithServiceProvider( serviceProviderInterface = CreateTaskPreprocessor.class, serviceProviders = DummyPriorityServiceProvider1.class) - class ExampleClassWithServiceProviders {} + class ExampleTestClassWithServiceProviders {} - ThrowingCallable call = () -> extractServiceProviders(ExampleClassWithServiceProviders.class); + ThrowingCallable call = + () -> extractServiceProviders(ExampleTestClassWithServiceProviders.class, Map.of()); assertThatThrownBy(call) .isInstanceOf(JUnitException.class) @@ -380,9 +423,10 @@ class ServiceProviderExtractorTest { @WithServiceProvider( serviceProviderInterface = CreateTaskPreprocessor.class, serviceProviders = {DummyTaskPreprocessor1.class, DummyPriorityServiceProvider1.class}) - class ExampleClassWithServiceProviders {} + class ExampleTestClassWithServiceProviders {} - ThrowingCallable call = () -> extractServiceProviders(ExampleClassWithServiceProviders.class); + ThrowingCallable call = + () -> extractServiceProviders(ExampleTestClassWithServiceProviders.class, Map.of()); assertThatThrownBy(call) .isInstanceOf(JUnitException.class) @@ -390,5 +434,29 @@ class ServiceProviderExtractorTest { "At least one ServiceProvider does not implement the requested SPI '%s'", CreateTaskPreprocessor.class); } + + @Test + void should_ThrowException_When_LocalServiceProviderUsesAMethodVariable() { + String methodVariable = "foobar"; + class LocalCreateTaskPreprocessor implements CreateTaskPreprocessor { + @Override + public void processTaskBeforeCreation(Task taskToProcess) { + // implementation not important for the tests + taskToProcess.setOwner(methodVariable); + } + } + + @WithServiceProvider( + serviceProviderInterface = CreateTaskPreprocessor.class, + serviceProviders = LocalCreateTaskPreprocessor.class) + class ExampleTestClassWithServiceProviders {} + + ThrowingCallable call = + () -> extractServiceProviders(ExampleTestClassWithServiceProviders.class, Map.of()); + + assertThatThrownBy(call) + .isInstanceOf(JUnitException.class) + .hasMessage("test-api does not support local class which accesses method variables"); + } } }