Verify registration of runtime hints for Bean Overrides

See gh-32933
This commit is contained in:
Sam Brannen
2024-10-08 14:26:52 +02:00
parent 6a2e234a87
commit 9a4be95dee
4 changed files with 233 additions and 30 deletions

View File

@@ -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",
};
}

View File

@@ -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";
}
}
}

View File

@@ -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";
}
}

View File

@@ -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();
}