From bf120cf44bcb63bc522f5374fbfef15dbff0d779 Mon Sep 17 00:00:00 2001 From: Lukas Dohmen Date: Wed, 22 May 2024 17:37:43 +0200 Subject: [PATCH] GH-466 - Allow using ApplicationModuleTest outside of module packages. --- .../integration/OrderIntegrationTests.java | 37 +++++++++++++++++++ .../modulith/test/ApplicationModuleTest.java | 8 ++++ .../modulith/test/ModuleTestExecution.java | 27 ++++++++++++-- 3 files changed, 69 insertions(+), 3 deletions(-) create mode 100644 spring-modulith-examples/spring-modulith-example-full/src/test/java/otherpackage/integration/OrderIntegrationTests.java diff --git a/spring-modulith-examples/spring-modulith-example-full/src/test/java/otherpackage/integration/OrderIntegrationTests.java b/spring-modulith-examples/spring-modulith-example-full/src/test/java/otherpackage/integration/OrderIntegrationTests.java new file mode 100644 index 00000000..0ba47905 --- /dev/null +++ b/spring-modulith-examples/spring-modulith-example-full/src/test/java/otherpackage/integration/OrderIntegrationTests.java @@ -0,0 +1,37 @@ +/* + * Copyright 2022-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package otherpackage.integration; + +import example.order.Order; +import example.order.OrderManagement; +import lombok.RequiredArgsConstructor; +import org.junit.jupiter.api.Test; +import org.springframework.modulith.test.ApplicationModuleTest; + +/** + * Test to showcase {@link ApplicationModuleTest} outside of a module package + */ +@ApplicationModuleTest(module = "order", classes = {example.Application.class}) +@RequiredArgsConstructor +class OrderIntegrationTests { + + private final OrderManagement orders; + + @Test + void completesOrder() { + orders.complete(new Order()); + } +} diff --git a/spring-modulith-test/src/main/java/org/springframework/modulith/test/ApplicationModuleTest.java b/spring-modulith-test/src/main/java/org/springframework/modulith/test/ApplicationModuleTest.java index 0fb0264c..8aa53e83 100644 --- a/spring-modulith-test/src/main/java/org/springframework/modulith/test/ApplicationModuleTest.java +++ b/spring-modulith-test/src/main/java/org/springframework/modulith/test/ApplicationModuleTest.java @@ -76,6 +76,14 @@ public @interface ApplicationModuleTest { */ String[] extraIncludes() default {}; + /** + * Logical name of the module in case {@link ApplicationModuleTest} will be used outside a module package + */ + String module() default ""; + + @AliasFor(annotation = SpringBootTest.class) + Class[] classes() default {}; + /** * The type of web environment to create when applicable. Defaults to {@link WebEnvironment#MOCK}. * diff --git a/spring-modulith-test/src/main/java/org/springframework/modulith/test/ModuleTestExecution.java b/spring-modulith-test/src/main/java/org/springframework/modulith/test/ModuleTestExecution.java index 462e9ba0..589c6f29 100644 --- a/spring-modulith-test/src/main/java/org/springframework/modulith/test/ModuleTestExecution.java +++ b/spring-modulith-test/src/main/java/org/springframework/modulith/test/ModuleTestExecution.java @@ -34,6 +34,7 @@ import org.springframework.modulith.core.ApplicationModule; import org.springframework.modulith.core.ApplicationModules; import org.springframework.modulith.core.JavaPackage; import org.springframework.modulith.test.ApplicationModuleTest.BootstrapMode; +import org.springframework.util.StringUtils; import org.springframework.util.function.SingletonSupplier; /** @@ -94,12 +95,21 @@ public class ModuleTestExecution implements Iterable { var annotation = AnnotatedElementUtils.findMergedAnnotation(type, ApplicationModuleTest.class); var packageName = type.getPackage().getName(); + var optionalModulithType = findSpringBootApplicationByClasses(annotation); - var modulithType = MODULITH_TYPES.computeIfAbsent(type, - it -> new AnnotatedClassFinder(SpringBootApplication.class).findFromPackage(packageName)); + var modulithType = optionalModulithType.orElseGet(() -> MODULITH_TYPES.computeIfAbsent(type, + it -> new AnnotatedClassFinder(SpringBootApplication.class).findFromPackage(packageName))); var modules = ApplicationModules.of(modulithType); - var module = modules.getModuleForPackage(packageName).orElseThrow( // + + var moduleName = annotation.module(); + ApplicationModule module; + if (StringUtils.hasText(moduleName)) { + module = modules.getModuleByName(moduleName).orElseThrow( // + () -> new IllegalStateException(String.format("Unable to find module %s!", moduleName))); + } else { + module = modules.getModuleForPackage(packageName).orElseThrow( // () -> new IllegalStateException(String.format("Package %s is not part of any module!", packageName))); + } return EXECUTIONS.computeIfAbsent(new Key(module.getBasePackage().getName(), annotation), it -> new ModuleTestExecution(annotation, modules, module)); @@ -230,5 +240,16 @@ public class ModuleTestExecution implements Iterable { .flatMap(Optional::stream); } + private static Optional> findSpringBootApplicationByClasses(ApplicationModuleTest annotation) { + for (Class clazz : annotation.classes()) { + Class modulithType = MODULITH_TYPES.computeIfAbsent(clazz, + it -> new AnnotatedClassFinder(SpringBootApplication.class).findFromPackage(clazz.getPackageName())); + if (modulithType != null) { + return Optional.of(modulithType); + } + } + return Optional.empty(); + } + private static record Key(String moduleBasePackage, ApplicationModuleTest annotation) {} }