diff --git a/spring-test/src/test/java/org/springframework/test/context/aot/TestContextAotGeneratorIntegrationTests.java b/spring-test/src/test/java/org/springframework/test/context/aot/TestContextAotGeneratorIntegrationTests.java index 4ab72df7e6..4d791caeee 100644 --- a/spring-test/src/test/java/org/springframework/test/context/aot/TestContextAotGeneratorIntegrationTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/aot/TestContextAotGeneratorIntegrationTests.java @@ -17,6 +17,7 @@ package org.springframework.test.context.aot; import java.lang.annotation.Annotation; +import java.lang.reflect.Proxy; import java.util.ArrayList; import java.util.List; import java.util.Set; @@ -24,7 +25,10 @@ import java.util.stream.Stream; import javax.sql.DataSource; +import org.assertj.core.util.Arrays; +import org.easymock.EasyMockSupport; import org.junit.jupiter.api.Test; +import org.mockito.Mockito; import org.springframework.aot.AotDetector; import org.springframework.aot.generate.DefaultGenerationContext; @@ -37,6 +41,7 @@ import org.springframework.aot.test.generate.CompilerFiles; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextInitializer; import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.aot.AbstractAotProcessor; import org.springframework.core.test.tools.CompileWithForkedClassLoader; import org.springframework.core.test.tools.TestCompiler; import org.springframework.javapoet.ClassName; @@ -47,6 +52,9 @@ import org.springframework.test.context.aot.samples.basic.BasicSpringJupiterShar import org.springframework.test.context.aot.samples.basic.BasicSpringJupiterTests; import org.springframework.test.context.aot.samples.basic.BasicSpringTestNGTests; import org.springframework.test.context.aot.samples.basic.BasicSpringVintageTests; +import org.springframework.test.context.aot.samples.bean.override.EasyMockBeanJupiterTests; +import org.springframework.test.context.aot.samples.bean.override.MockitoBeanJupiterTests; +import org.springframework.test.context.aot.samples.common.GreetingService; import org.springframework.test.context.aot.samples.common.MessageService; import org.springframework.test.context.aot.samples.jdbc.SqlScriptsSpringJupiterTests; import org.springframework.test.context.aot.samples.web.WebSpringJupiterTests; @@ -67,6 +75,7 @@ import static org.springframework.aot.hint.MemberCategory.INVOKE_DECLARED_CONSTR import static org.springframework.aot.hint.MemberCategory.INVOKE_DECLARED_METHODS; import static org.springframework.aot.hint.MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS; import static org.springframework.aot.hint.MemberCategory.INVOKE_PUBLIC_METHODS; +import static org.springframework.aot.hint.predicate.RuntimeHintsPredicates.proxies; import static org.springframework.aot.hint.predicate.RuntimeHintsPredicates.reflection; import static org.springframework.aot.hint.predicate.RuntimeHintsPredicates.resource; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; @@ -97,6 +106,8 @@ class TestContextAotGeneratorIntegrationTests extends AbstractAotTests { BasicSpringJupiterTests.NestedTests.class, BasicSpringTestNGTests.class, BasicSpringVintageTests.class, + EasyMockBeanJupiterTests.class, + MockitoBeanJupiterTests.class, SqlScriptsSpringJupiterTests.class, XmlSpringJupiterTests.class, WebSpringJupiterTests.class); @@ -104,7 +115,16 @@ class TestContextAotGeneratorIntegrationTests extends AbstractAotTests { InMemoryGeneratedFiles generatedFiles = new InMemoryGeneratedFiles(); TestContextAotGenerator generator = new TestContextAotGenerator(generatedFiles); - generator.processAheadOfTime(testClasses.stream().sorted(comparing(Class::getName))); + try { + // Emulate AbstractAotProcessor.process(). + System.setProperty(AbstractAotProcessor.AOT_PROCESSING, "true"); + + generator.processAheadOfTime(testClasses.stream().sorted(comparing(Class::getName))); + } + finally { + // Emulate AbstractAotProcessor.process(). + System.clearProperty(AbstractAotProcessor.AOT_PROCESSING); + } assertRuntimeHints(generator.getRuntimeHints()); @@ -142,6 +162,12 @@ class TestContextAotGeneratorIntegrationTests extends AbstractAotTests { else if (testClass.getPackageName().contains("jdbc")) { assertContextForJdbcTests(context); } + else if (testClass.equals(EasyMockBeanJupiterTests.class)) { + assertContextForEasyMockBeanOverrideTests(context); + } + else if (testClass.equals(MockitoBeanJupiterTests.class)) { + assertContextForMockitoBeanOverrideTests(context); + } else { assertContextForBasicTests(context); } @@ -243,6 +269,15 @@ class TestContextAotGeneratorIntegrationTests extends AbstractAotTests { .accepts(runtimeHints); assertThat(resource().forResource("org/springframework/test/context/aot/samples/jdbc/SqlScriptsSpringJupiterTests.test.sql")) .accepts(runtimeHints); + + // @BeanOverride(value = ...) + assertReflectionRegistered(runtimeHints, "org.springframework.test.context.bean.override.mockito.MockitoBeanOverrideProcessor", + INVOKE_DECLARED_CONSTRUCTORS); + + // GenericApplicationContext.preDetermineBeanTypes() should have registered proxy + // hints for the EasyMock interface-based mocks. + assertProxyRegistered(runtimeHints, GreetingService.class); + assertProxyRegistered(runtimeHints, MessageService.class); } private static void assertReflectionRegistered(RuntimeHints runtimeHints, String type) { @@ -267,6 +302,13 @@ class TestContextAotGeneratorIntegrationTests extends AbstractAotTests { assertReflectionRegistered(runtimeHints, annotationType, INVOKE_DECLARED_METHODS); } + private static void assertProxyRegistered(RuntimeHints runtimeHints, Class... interfaces) { + assertThat(proxies().forInterfaces(interfaces)) + .as("Proxy hint for %s", Arrays.asList(interfaces)) + .accepts(runtimeHints); + } + + @Test void processAheadOfTimeWithBasicTests() { @@ -297,6 +339,24 @@ class TestContextAotGeneratorIntegrationTests extends AbstractAotTests { assertThat(context.getBean(DataSource.class)).as("DataSource").isNotNull(); } + private void assertContextForEasyMockBeanOverrideTests(ApplicationContext context) { + GreetingService greetingService = context.getBean(GreetingService.class); + MessageService messageService = context.getBean(MessageService.class); + + assertThat(EasyMockSupport.isAMock(greetingService)).as("EasyMock mock").isTrue(); + assertThat(EasyMockSupport.isAMock(messageService)).as("EasyMock mock").isTrue(); + assertThat(Proxy.isProxyClass(greetingService.getClass())).as("JDK proxy").isTrue(); + assertThat(Proxy.isProxyClass(messageService.getClass())).as("JDK proxy").isTrue(); + } + + private void assertContextForMockitoBeanOverrideTests(ApplicationContext context) { + GreetingService greetingService = context.getBean(GreetingService.class); + MessageService messageService = context.getBean(MessageService.class); + + assertThat(Mockito.mockingDetails(greetingService).isMock()).as("Mockito mock").isTrue(); + assertThat(Mockito.mockingDetails(messageService).isMock()).as("Mockito mock").isTrue(); + } + private void assertContextForWebTests(WebApplicationContext wac) throws Exception { assertThat(wac.getEnvironment().getProperty("test.engine")).as("Environment").isNotNull(); @@ -364,10 +424,19 @@ class TestContextAotGeneratorIntegrationTests extends AbstractAotTests { testClasses.forEach(testClass -> { DefaultGenerationContext generationContext = generator.createGenerationContext(testClass); MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass); - ClassName className = generator.processAheadOfTime(mergedConfig, generationContext); - assertThat(className).isNotNull(); - mappings.add(new Mapping(mergedConfig, className)); - generationContext.writeGeneratedContent(); + try { + // Emulate AbstractAotProcessor.process(). + System.setProperty(AbstractAotProcessor.AOT_PROCESSING, "true"); + + ClassName className = generator.processAheadOfTime(mergedConfig, generationContext); + assertThat(className).isNotNull(); + mappings.add(new Mapping(mergedConfig, className)); + generationContext.writeGeneratedContent(); + } + finally { + // Emulate AbstractAotProcessor.process(). + System.clearProperty(AbstractAotProcessor.AOT_PROCESSING); + } }); return mappings; } @@ -422,29 +491,43 @@ class TestContextAotGeneratorIntegrationTests extends AbstractAotTests { "org/springframework/test/context/aot/samples/basic/BasicSpringVintageTests__TestContext004_BeanFactoryRegistrations.java", "org/springframework/test/context/aot/samples/basic/BasicTestConfiguration__TestContext004_BeanDefinitions.java", "org/springframework/test/context/support/DynamicPropertyRegistrarBeanInitializer__TestContext004_BeanDefinitions.java", - // SqlScriptsSpringJupiterTests + // EasyMockBeanJupiterTests "org/springframework/context/event/DefaultEventListenerFactory__TestContext005_BeanDefinitions.java", "org/springframework/context/event/EventListenerMethodProcessor__TestContext005_BeanDefinitions.java", - "org/springframework/test/context/aot/samples/jdbc/SqlScriptsSpringJupiterTests__TestContext005_ApplicationContextInitializer.java", - "org/springframework/test/context/aot/samples/jdbc/SqlScriptsSpringJupiterTests__TestContext005_BeanFactoryRegistrations.java", - "org/springframework/test/context/jdbc/EmptyDatabaseConfig__TestContext005_BeanDefinitions.java", + "org/springframework/test/context/aot/samples/bean/override/EasyMockBeanJupiterTests__TestContext005_ApplicationContextInitializer.java", + "org/springframework/test/context/aot/samples/bean/override/EasyMockBeanJupiterTests__TestContext005_BeanDefinitions.java", + "org/springframework/test/context/aot/samples/bean/override/EasyMockBeanJupiterTests__TestContext005_BeanFactoryRegistrations.java", "org/springframework/test/context/support/DynamicPropertyRegistrarBeanInitializer__TestContext005_BeanDefinitions.java", - // WebSpringJupiterTests + // MockitoBeanJupiterTests "org/springframework/context/event/DefaultEventListenerFactory__TestContext006_BeanDefinitions.java", "org/springframework/context/event/EventListenerMethodProcessor__TestContext006_BeanDefinitions.java", - "org/springframework/test/context/aot/samples/web/WebSpringJupiterTests__TestContext006_ApplicationContextInitializer.java", - "org/springframework/test/context/aot/samples/web/WebSpringJupiterTests__TestContext006_BeanFactoryRegistrations.java", - "org/springframework/test/context/aot/samples/web/WebTestConfiguration__TestContext006_BeanDefinitions.java", - "org/springframework/web/servlet/config/annotation/DelegatingWebMvcConfiguration__TestContext006_Autowiring.java", - "org/springframework/web/servlet/config/annotation/DelegatingWebMvcConfiguration__TestContext006_BeanDefinitions.java", + "org/springframework/test/context/aot/samples/bean/override/MockitoBeanJupiterTests__TestContext006_ApplicationContextInitializer.java", + "org/springframework/test/context/aot/samples/bean/override/MockitoBeanJupiterTests__TestContext006_BeanDefinitions.java", + "org/springframework/test/context/aot/samples/bean/override/MockitoBeanJupiterTests__TestContext006_BeanFactoryRegistrations.java", "org/springframework/test/context/support/DynamicPropertyRegistrarBeanInitializer__TestContext006_BeanDefinitions.java", - // XmlSpringJupiterTests + // SqlScriptsSpringJupiterTests "org/springframework/context/event/DefaultEventListenerFactory__TestContext007_BeanDefinitions.java", "org/springframework/context/event/EventListenerMethodProcessor__TestContext007_BeanDefinitions.java", - "org/springframework/test/context/aot/samples/common/DefaultMessageService__TestContext007_BeanDefinitions.java", - "org/springframework/test/context/aot/samples/xml/XmlSpringJupiterTests__TestContext007_ApplicationContextInitializer.java", - "org/springframework/test/context/aot/samples/xml/XmlSpringJupiterTests__TestContext007_BeanFactoryRegistrations.java", + "org/springframework/test/context/aot/samples/jdbc/SqlScriptsSpringJupiterTests__TestContext007_ApplicationContextInitializer.java", + "org/springframework/test/context/aot/samples/jdbc/SqlScriptsSpringJupiterTests__TestContext007_BeanFactoryRegistrations.java", + "org/springframework/test/context/jdbc/EmptyDatabaseConfig__TestContext007_BeanDefinitions.java", "org/springframework/test/context/support/DynamicPropertyRegistrarBeanInitializer__TestContext007_BeanDefinitions.java", + // WebSpringJupiterTests + "org/springframework/context/event/DefaultEventListenerFactory__TestContext008_BeanDefinitions.java", + "org/springframework/context/event/EventListenerMethodProcessor__TestContext008_BeanDefinitions.java", + "org/springframework/test/context/aot/samples/web/WebSpringJupiterTests__TestContext008_ApplicationContextInitializer.java", + "org/springframework/test/context/aot/samples/web/WebSpringJupiterTests__TestContext008_BeanFactoryRegistrations.java", + "org/springframework/test/context/aot/samples/web/WebTestConfiguration__TestContext008_BeanDefinitions.java", + "org/springframework/test/context/support/DynamicPropertyRegistrarBeanInitializer__TestContext008_BeanDefinitions.java", + "org/springframework/web/servlet/config/annotation/DelegatingWebMvcConfiguration__TestContext008_Autowiring.java", + "org/springframework/web/servlet/config/annotation/DelegatingWebMvcConfiguration__TestContext008_BeanDefinitions.java", + // XmlSpringJupiterTests + "org/springframework/context/event/DefaultEventListenerFactory__TestContext009_BeanDefinitions.java", + "org/springframework/context/event/EventListenerMethodProcessor__TestContext009_BeanDefinitions.java", + "org/springframework/test/context/aot/samples/common/DefaultMessageService__TestContext009_BeanDefinitions.java", + "org/springframework/test/context/aot/samples/xml/XmlSpringJupiterTests__TestContext009_ApplicationContextInitializer.java", + "org/springframework/test/context/aot/samples/xml/XmlSpringJupiterTests__TestContext009_BeanFactoryRegistrations.java", + "org/springframework/test/context/support/DynamicPropertyRegistrarBeanInitializer__TestContext009_BeanDefinitions.java", }; } diff --git a/spring-test/src/test/java/org/springframework/test/context/aot/samples/bean/override/EasyMockBeanJupiterTests.java b/spring-test/src/test/java/org/springframework/test/context/aot/samples/bean/override/EasyMockBeanJupiterTests.java new file mode 100644 index 0000000000..39d7dddd71 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/aot/samples/bean/override/EasyMockBeanJupiterTests.java @@ -0,0 +1,75 @@ +/* + * Copyright 2002-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 org.springframework.test.context.aot.samples.bean.override; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.context.aot.samples.common.GreetingService; +import org.springframework.test.context.aot.samples.common.MessageService; +import org.springframework.test.context.bean.override.easymock.EasyMockBean; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.easymock.EasyMock.expect; +import static org.easymock.EasyMock.replay; + +/** + * @author Sam Brannen + * @since 6.2 + */ +@SpringJUnitConfig +public class EasyMockBeanJupiterTests { + + /** + * Mock for nonexistent bean. + */ + @EasyMockBean + GreetingService greetingService; + + /** + * Mock for existing bean. + */ + @EasyMockBean + MessageService messageService; + + @BeforeEach + void configureMocks(@Autowired GreetingService greetingService, @Autowired MessageService messageService) { + expect(greetingService.greeting()).andReturn("enigma"); + expect(messageService.generateMessage()).andReturn("override"); + replay(greetingService, messageService); + } + + @Test + void test() { + assertThat(greetingService.greeting()).isEqualTo("enigma"); + assertThat(messageService.generateMessage()).isEqualTo("override"); + } + + @Configuration(proxyBeanMethods = false) + static class Config { + + @Bean + MessageService messageService() { + return () -> "prod"; + } + } + +} diff --git a/spring-test/src/test/java/org/springframework/test/context/aot/samples/bean/override/convention/TestBeanJupiterTests.java b/spring-test/src/test/java/org/springframework/test/context/aot/samples/bean/override/MockitoBeanJupiterTests.java similarity index 51% rename from spring-test/src/test/java/org/springframework/test/context/aot/samples/bean/override/convention/TestBeanJupiterTests.java rename to spring-test/src/test/java/org/springframework/test/context/aot/samples/bean/override/MockitoBeanJupiterTests.java index 464a5cdce7..9cd784a775 100644 --- a/spring-test/src/test/java/org/springframework/test/context/aot/samples/bean/override/convention/TestBeanJupiterTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/aot/samples/bean/override/MockitoBeanJupiterTests.java @@ -14,42 +14,59 @@ * limitations under the License. */ -package org.springframework.test.context.aot.samples.bean.override.convention; +package org.springframework.test.context.aot.samples.bean.override; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.test.context.bean.override.convention.TestBean; +import org.springframework.test.context.aot.samples.common.GreetingService; +import org.springframework.test.context.aot.samples.common.MessageService; +import org.springframework.test.context.bean.override.mockito.MockitoBean; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.BDDMockito.when; /** * @author Sam Brannen * @since 6.2 */ @SpringJUnitConfig -public class TestBeanJupiterTests { +public class MockitoBeanJupiterTests { - @TestBean - String magicBean; + /** + * Mock for nonexistent bean. + */ + @MockitoBean(enforceOverride = false) + GreetingService greetingService; - static String magicBean() { - return "enigma-override"; + /** + * Mock for existing bean. + */ + @MockitoBean + MessageService messageService; + + @BeforeEach + void configureMocks(@Autowired GreetingService greetingService, @Autowired MessageService messageService) { + when(greetingService.greeting()).thenReturn("enigma"); + when(messageService.generateMessage()).thenReturn("override"); } @Test - void tests() { - assertThat(magicBean).isEqualTo("enigma-override"); + void test() { + assertThat(greetingService.greeting()).isEqualTo("enigma"); + assertThat(messageService.generateMessage()).isEqualTo("override"); } @Configuration(proxyBeanMethods = false) static class Config { @Bean - String magicBean() { - return "enigma"; + MessageService messageService() { + return () -> "prod"; } } diff --git a/spring-test/src/test/java/org/springframework/test/context/aot/samples/common/GreetingService.java b/spring-test/src/test/java/org/springframework/test/context/aot/samples/common/GreetingService.java new file mode 100644 index 0000000000..53e31b7ed9 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/aot/samples/common/GreetingService.java @@ -0,0 +1,28 @@ +/* + * Copyright 2002-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 org.springframework.test.context.aot.samples.common; + +/** + * @author Sam Brannen + * @since 6.2 + */ +@FunctionalInterface +public interface GreetingService { + + String greeting(); + +}