diff --git a/spring-test/src/main/java/org/springframework/test/context/aot/TestContextAotGenerator.java b/spring-test/src/main/java/org/springframework/test/context/aot/TestContextAotGenerator.java index b4eaf1e756..b53cca2ff4 100644 --- a/spring-test/src/main/java/org/springframework/test/context/aot/TestContextAotGenerator.java +++ b/spring-test/src/main/java/org/springframework/test/context/aot/TestContextAotGenerator.java @@ -28,6 +28,7 @@ import org.springframework.aot.generate.GeneratedClasses; import org.springframework.aot.generate.GeneratedFiles; import org.springframework.aot.generate.GenerationContext; import org.springframework.aot.hint.MemberCategory; +import org.springframework.aot.hint.ReflectionHints; import org.springframework.aot.hint.RuntimeHints; import org.springframework.aot.hint.TypeReference; import org.springframework.context.ApplicationContext; @@ -173,6 +174,8 @@ public class TestContextAotGenerator { Consider annotating test class [%s] with @ContextConfiguration or \ @ContextHierarchy.""".formatted(testClass.getName())); + registerDeclaredConstructors(contextLoader.getClass()); + if (contextLoader instanceof AotContextLoader aotContextLoader) { try { ApplicationContext context = aotContextLoader.loadContextForAotProcessing(mergedConfig); @@ -224,4 +227,9 @@ public class TestContextAotGenerator { .registerType(TypeReference.of(className), MemberCategory.INVOKE_PUBLIC_METHODS); } + private void registerDeclaredConstructors(Class type) { + ReflectionHints reflectionHints = this.runtimeHints.reflection(); + reflectionHints.registerType(type, MemberCategory.INVOKE_DECLARED_CONSTRUCTORS); + } + } diff --git a/spring-test/src/main/java/org/springframework/test/context/aot/hint/TestContextRuntimeHints.java b/spring-test/src/main/java/org/springframework/test/context/aot/hint/TestContextRuntimeHints.java index 31ac273464..86c03df0cf 100644 --- a/spring-test/src/main/java/org/springframework/test/context/aot/hint/TestContextRuntimeHints.java +++ b/spring-test/src/main/java/org/springframework/test/context/aot/hint/TestContextRuntimeHints.java @@ -48,9 +48,7 @@ class TestContextRuntimeHints implements RuntimeHintsRegistrar { // Loaded reflectively in BootstrapUtils org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.class, // Loaded reflectively in BootstrapUtils - org.springframework.test.context.support.DefaultBootstrapContext.class, - // Loaded reflectively in AbstractTestContextBootstrapper#resolveContextLoader() - org.springframework.test.context.support.DelegatingSmartContextLoader.class + org.springframework.test.context.support.DefaultBootstrapContext.class ); registerDeclaredConstructors(reflectionHints, @@ -59,10 +57,6 @@ class TestContextRuntimeHints implements RuntimeHintsRegistrar { ); if (servletPresent) { - registerPublicConstructors(reflectionHints, - // Loaded reflectively in AbstractTestContextBootstrapper#resolveContextLoader() - "org.springframework.test.context.web.WebDelegatingSmartContextLoader" - ); registerDeclaredConstructors(reflectionHints, // Loaded reflectively in BootstrapUtils "org.springframework.test.context.web.WebTestContextBootstrapper" @@ -92,10 +86,6 @@ class TestContextRuntimeHints implements RuntimeHintsRegistrar { registerPublicConstructors(reflectionHints, TypeReference.listOf(types)); } - private static void registerPublicConstructors(ReflectionHints reflectionHints, String... classNames) { - registerPublicConstructors(reflectionHints, listOf(classNames)); - } - private static void registerPublicConstructors(ReflectionHints reflectionHints, Iterable types) { reflectionHints.registerTypes(types, TypeHint.builtWith(MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS)); } diff --git a/spring-test/src/test/java/org/springframework/test/context/aot/TestContextAotGeneratorTests.java b/spring-test/src/test/java/org/springframework/test/context/aot/TestContextAotGeneratorTests.java index b5567fe9b2..7ddf70477e 100644 --- a/spring-test/src/test/java/org/springframework/test/context/aot/TestContextAotGeneratorTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/aot/TestContextAotGeneratorTests.java @@ -82,7 +82,8 @@ class TestContextAotGeneratorTests extends AbstractAotTests { BasicSpringJupiterTests.class, BasicSpringJupiterTests.NestedTests.class, BasicSpringTestNGTests.class, - BasicSpringVintageTests.class); + BasicSpringVintageTests.class, + WebSpringJupiterTests.class); InMemoryGeneratedFiles generatedFiles = new InMemoryGeneratedFiles(); TestContextAotGenerator generator = new TestContextAotGenerator(generatedFiles); @@ -92,7 +93,7 @@ class TestContextAotGeneratorTests extends AbstractAotTests { assertRuntimeHints(generator.getRuntimeHints()); List sourceFiles = generatedFiles.getGeneratedFiles(Kind.SOURCE).keySet().stream().toList(); - assertThat(sourceFiles).containsExactlyInAnyOrder(expectedSourceFilesForBasicSpringTests); + assertThat(sourceFiles).containsExactlyInAnyOrder(expectedSourceFiles); TestCompiler.forSystem().withFiles(generatedFiles).compile(ThrowingConsumer.of(compiled -> { AotTestMappings aotTestMappings = new AotTestMappings(); @@ -103,7 +104,12 @@ class TestContextAotGeneratorTests extends AbstractAotTests { assertThat(contextInitializer).isNotNull(); ApplicationContext context = ((AotContextLoader) mergedConfig.getContextLoader()) .loadContextForAotRuntime(mergedConfig, contextInitializer); - assertContextForBasicTests(context); + if (context instanceof WebApplicationContext wac) { + assertContextForBasicWebTests(wac); + } + else { + assertContextForBasicTests(context); + } } })); } @@ -113,16 +119,17 @@ class TestContextAotGeneratorTests extends AbstractAotTests { Set.of( org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.class, - org.springframework.test.context.support.DefaultBootstrapContext.class, - org.springframework.test.context.support.DelegatingSmartContextLoader.class, - org.springframework.test.context.web.WebDelegatingSmartContextLoader.class + org.springframework.test.context.support.DefaultBootstrapContext.class ).forEach(type -> assertReflectionRegistered(runtimeHints, type, INVOKE_PUBLIC_CONSTRUCTORS)); Set.of( + org.springframework.test.context.support.AnnotationConfigContextLoader.class, org.springframework.test.context.support.DefaultTestContextBootstrapper.class, - org.springframework.test.context.web.WebTestContextBootstrapper.class, + org.springframework.test.context.support.DelegatingSmartContextLoader.class, org.springframework.test.context.support.GenericGroovyXmlContextLoader.class, - org.springframework.test.context.web.GenericGroovyXmlWebContextLoader.class + org.springframework.test.context.web.GenericGroovyXmlWebContextLoader.class, + org.springframework.test.context.web.WebDelegatingSmartContextLoader.class, + org.springframework.test.context.web.WebTestContextBootstrapper.class ).forEach(type -> assertReflectionRegistered(runtimeHints, type, INVOKE_DECLARED_CONSTRUCTORS)); Set.of( @@ -186,6 +193,13 @@ class TestContextAotGeneratorTests extends AbstractAotTests { assertThat(messageService.generateMessage()).isEqualTo("Hello, AOT!"); } + private void assertContextForBasicWebTests(WebApplicationContext wac) throws Exception { + assertThat(wac.getEnvironment().getProperty("test.engine")).as("Environment").isNotNull(); + + MockMvc mockMvc = webAppContextSetup(wac).build(); + mockMvc.perform(get("/hello")).andExpectAll(status().isOk(), content().string("Hello, AOT!")); + } + @Test void processAheadOfTimeWithXmlTests() { // We cannot parameterize with the test classes, since @CompileWithTargetClassAccess @@ -258,4 +272,43 @@ class TestContextAotGeneratorTests extends AbstractAotTests { record Mapping(MergedContextConfiguration mergedConfig, ClassName className) { } + private static final String[] expectedSourceFiles = { + // Global + "org/springframework/test/context/aot/AotTestMappings__Generated.java", + // BasicSpringJupiterSharedConfigTests + "org/springframework/context/event/DefaultEventListenerFactory__TestContext001_BeanDefinitions.java", + "org/springframework/context/event/EventListenerMethodProcessor__TestContext001_BeanDefinitions.java", + "org/springframework/test/context/aot/samples/basic/BasicSpringJupiterSharedConfigTests__TestContext001_ApplicationContextInitializer.java", + "org/springframework/test/context/aot/samples/basic/BasicSpringJupiterSharedConfigTests__TestContext001_BeanFactoryRegistrations.java", + "org/springframework/test/context/aot/samples/basic/BasicTestConfiguration__TestContext001_BeanDefinitions.java", + // BasicSpringJupiterTests -- not generated b/c already generated for BasicSpringJupiterSharedConfigTests. + // BasicSpringJupiterTests.NestedTests + "org/springframework/context/event/DefaultEventListenerFactory__TestContext002_BeanDefinitions.java", + "org/springframework/context/event/EventListenerMethodProcessor__TestContext002_BeanDefinitions.java", + "org/springframework/test/context/aot/samples/basic/BasicSpringJupiterTests_NestedTests__TestContext002_ApplicationContextInitializer.java", + "org/springframework/test/context/aot/samples/basic/BasicSpringJupiterTests_NestedTests__TestContext002_BeanFactoryRegistrations.java", + "org/springframework/test/context/aot/samples/basic/BasicTestConfiguration__TestContext002_BeanDefinitions.java", + // BasicSpringTestNGTests + "org/springframework/context/event/DefaultEventListenerFactory__TestContext003_BeanDefinitions.java", + "org/springframework/context/event/EventListenerMethodProcessor__TestContext003_BeanDefinitions.java", + "org/springframework/test/context/aot/samples/basic/BasicSpringTestNGTests__TestContext003_ApplicationContextInitializer.java", + "org/springframework/test/context/aot/samples/basic/BasicSpringTestNGTests__TestContext003_BeanFactoryRegistrations.java", + "org/springframework/test/context/aot/samples/basic/BasicTestConfiguration__TestContext003_BeanDefinitions.java", + // BasicSpringVintageTests + "org/springframework/context/event/DefaultEventListenerFactory__TestContext004_BeanDefinitions.java", + "org/springframework/context/event/EventListenerMethodProcessor__TestContext004_BeanDefinitions.java", + "org/springframework/test/context/aot/samples/basic/BasicSpringVintageTests__TestContext004_ApplicationContextInitializer.java", + "org/springframework/test/context/aot/samples/basic/BasicSpringVintageTests__TestContext004_BeanFactoryRegistrations.java", + "org/springframework/test/context/aot/samples/basic/BasicTestConfiguration__TestContext004_BeanDefinitions.java", + // WebSpringJupiterTests + "org/springframework/context/event/DefaultEventListenerFactory__TestContext005_BeanDefinitions.java", + "org/springframework/context/event/EventListenerMethodProcessor__TestContext005_BeanDefinitions.java", + "org/springframework/test/context/aot/samples/web/WebSpringJupiterTests__TestContext005_ApplicationContextInitializer.java", + "org/springframework/test/context/aot/samples/web/WebSpringJupiterTests__TestContext005_BeanFactoryRegistrations.java", + "org/springframework/test/context/aot/samples/web/WebTestConfiguration__TestContext005_BeanDefinitions.java", + "org/springframework/web/reactive/config/DelegatingWebFluxConfiguration__TestContext005_Autowiring.java", + "org/springframework/web/reactive/config/DelegatingWebFluxConfiguration__TestContext005_BeanDefinitions.java", + "org/springframework/web/reactive/config/WebFluxConfigurationSupport__TestContext005_BeanDefinitions.java" + }; + } diff --git a/spring-test/src/test/java/org/springframework/test/context/aot/samples/basic/BasicSpringVintageTests.java b/spring-test/src/test/java/org/springframework/test/context/aot/samples/basic/BasicSpringVintageTests.java index fe5244440c..709d20fddc 100644 --- a/spring-test/src/test/java/org/springframework/test/context/aot/samples/basic/BasicSpringVintageTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/aot/samples/basic/BasicSpringVintageTests.java @@ -25,6 +25,7 @@ import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.TestPropertySource; import org.springframework.test.context.aot.samples.common.MessageService; import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.context.support.AnnotationConfigContextLoader; import static org.assertj.core.api.Assertions.assertThat; @@ -33,7 +34,7 @@ import static org.assertj.core.api.Assertions.assertThat; * @since 6.0 */ @RunWith(SpringRunner.class) -@ContextConfiguration(classes = BasicTestConfiguration.class) +@ContextConfiguration(classes = BasicTestConfiguration.class, loader = AnnotationConfigContextLoader.class) @TestPropertySource(properties = "test.engine = vintage") public class BasicSpringVintageTests {