Skip to content

Commit 9a4be95

Browse files
committed
Verify registration of runtime hints for Bean Overrides
See gh-32933
1 parent 6a2e234 commit 9a4be95

File tree

4 files changed

+233
-30
lines changed

4 files changed

+233
-30
lines changed

spring-test/src/test/java/org/springframework/test/context/aot/TestContextAotGeneratorIntegrationTests.java

Lines changed: 102 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,18 @@
1717
package org.springframework.test.context.aot;
1818

1919
import java.lang.annotation.Annotation;
20+
import java.lang.reflect.Proxy;
2021
import java.util.ArrayList;
2122
import java.util.List;
2223
import java.util.Set;
2324
import java.util.stream.Stream;
2425

2526
import javax.sql.DataSource;
2627

28+
import org.assertj.core.util.Arrays;
29+
import org.easymock.EasyMockSupport;
2730
import org.junit.jupiter.api.Test;
31+
import org.mockito.Mockito;
2832

2933
import org.springframework.aot.AotDetector;
3034
import org.springframework.aot.generate.DefaultGenerationContext;
@@ -37,6 +41,7 @@
3741
import org.springframework.context.ApplicationContext;
3842
import org.springframework.context.ApplicationContextInitializer;
3943
import org.springframework.context.ConfigurableApplicationContext;
44+
import org.springframework.context.aot.AbstractAotProcessor;
4045
import org.springframework.core.test.tools.CompileWithForkedClassLoader;
4146
import org.springframework.core.test.tools.TestCompiler;
4247
import org.springframework.javapoet.ClassName;
@@ -47,6 +52,9 @@
4752
import org.springframework.test.context.aot.samples.basic.BasicSpringJupiterTests;
4853
import org.springframework.test.context.aot.samples.basic.BasicSpringTestNGTests;
4954
import org.springframework.test.context.aot.samples.basic.BasicSpringVintageTests;
55+
import org.springframework.test.context.aot.samples.bean.override.EasyMockBeanJupiterTests;
56+
import org.springframework.test.context.aot.samples.bean.override.MockitoBeanJupiterTests;
57+
import org.springframework.test.context.aot.samples.common.GreetingService;
5058
import org.springframework.test.context.aot.samples.common.MessageService;
5159
import org.springframework.test.context.aot.samples.jdbc.SqlScriptsSpringJupiterTests;
5260
import org.springframework.test.context.aot.samples.web.WebSpringJupiterTests;
@@ -67,6 +75,7 @@
6775
import static org.springframework.aot.hint.MemberCategory.INVOKE_DECLARED_METHODS;
6876
import static org.springframework.aot.hint.MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS;
6977
import static org.springframework.aot.hint.MemberCategory.INVOKE_PUBLIC_METHODS;
78+
import static org.springframework.aot.hint.predicate.RuntimeHintsPredicates.proxies;
7079
import static org.springframework.aot.hint.predicate.RuntimeHintsPredicates.reflection;
7180
import static org.springframework.aot.hint.predicate.RuntimeHintsPredicates.resource;
7281
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
@@ -97,14 +106,25 @@ void endToEndTests() {
97106
BasicSpringJupiterTests.NestedTests.class,
98107
BasicSpringTestNGTests.class,
99108
BasicSpringVintageTests.class,
109+
EasyMockBeanJupiterTests.class,
110+
MockitoBeanJupiterTests.class,
100111
SqlScriptsSpringJupiterTests.class,
101112
XmlSpringJupiterTests.class,
102113
WebSpringJupiterTests.class);
103114

104115
InMemoryGeneratedFiles generatedFiles = new InMemoryGeneratedFiles();
105116
TestContextAotGenerator generator = new TestContextAotGenerator(generatedFiles);
106117

107-
generator.processAheadOfTime(testClasses.stream().sorted(comparing(Class::getName)));
118+
try {
119+
// Emulate AbstractAotProcessor.process().
120+
System.setProperty(AbstractAotProcessor.AOT_PROCESSING, "true");
121+
122+
generator.processAheadOfTime(testClasses.stream().sorted(comparing(Class::getName)));
123+
}
124+
finally {
125+
// Emulate AbstractAotProcessor.process().
126+
System.clearProperty(AbstractAotProcessor.AOT_PROCESSING);
127+
}
108128

109129
assertRuntimeHints(generator.getRuntimeHints());
110130

@@ -142,6 +162,12 @@ void endToEndTests() {
142162
else if (testClass.getPackageName().contains("jdbc")) {
143163
assertContextForJdbcTests(context);
144164
}
165+
else if (testClass.equals(EasyMockBeanJupiterTests.class)) {
166+
assertContextForEasyMockBeanOverrideTests(context);
167+
}
168+
else if (testClass.equals(MockitoBeanJupiterTests.class)) {
169+
assertContextForMockitoBeanOverrideTests(context);
170+
}
145171
else {
146172
assertContextForBasicTests(context);
147173
}
@@ -243,6 +269,15 @@ private static void assertRuntimeHints(RuntimeHints runtimeHints) {
243269
.accepts(runtimeHints);
244270
assertThat(resource().forResource("org/springframework/test/context/aot/samples/jdbc/SqlScriptsSpringJupiterTests.test.sql"))
245271
.accepts(runtimeHints);
272+
273+
// @BeanOverride(value = ...)
274+
assertReflectionRegistered(runtimeHints, "org.springframework.test.context.bean.override.mockito.MockitoBeanOverrideProcessor",
275+
INVOKE_DECLARED_CONSTRUCTORS);
276+
277+
// GenericApplicationContext.preDetermineBeanTypes() should have registered proxy
278+
// hints for the EasyMock interface-based mocks.
279+
assertProxyRegistered(runtimeHints, GreetingService.class);
280+
assertProxyRegistered(runtimeHints, MessageService.class);
246281
}
247282

248283
private static void assertReflectionRegistered(RuntimeHints runtimeHints, String type) {
@@ -267,6 +302,13 @@ private static void assertAnnotationRegistered(RuntimeHints runtimeHints, Class<
267302
assertReflectionRegistered(runtimeHints, annotationType, INVOKE_DECLARED_METHODS);
268303
}
269304

305+
private static void assertProxyRegistered(RuntimeHints runtimeHints, Class<?>... interfaces) {
306+
assertThat(proxies().forInterfaces(interfaces))
307+
.as("Proxy hint for %s", Arrays.asList(interfaces))
308+
.accepts(runtimeHints);
309+
}
310+
311+
270312

271313
@Test
272314
void processAheadOfTimeWithBasicTests() {
@@ -297,6 +339,24 @@ private void assertContextForJdbcTests(ApplicationContext context) {
297339
assertThat(context.getBean(DataSource.class)).as("DataSource").isNotNull();
298340
}
299341

342+
private void assertContextForEasyMockBeanOverrideTests(ApplicationContext context) {
343+
GreetingService greetingService = context.getBean(GreetingService.class);
344+
MessageService messageService = context.getBean(MessageService.class);
345+
346+
assertThat(EasyMockSupport.isAMock(greetingService)).as("EasyMock mock").isTrue();
347+
assertThat(EasyMockSupport.isAMock(messageService)).as("EasyMock mock").isTrue();
348+
assertThat(Proxy.isProxyClass(greetingService.getClass())).as("JDK proxy").isTrue();
349+
assertThat(Proxy.isProxyClass(messageService.getClass())).as("JDK proxy").isTrue();
350+
}
351+
352+
private void assertContextForMockitoBeanOverrideTests(ApplicationContext context) {
353+
GreetingService greetingService = context.getBean(GreetingService.class);
354+
MessageService messageService = context.getBean(MessageService.class);
355+
356+
assertThat(Mockito.mockingDetails(greetingService).isMock()).as("Mockito mock").isTrue();
357+
assertThat(Mockito.mockingDetails(messageService).isMock()).as("Mockito mock").isTrue();
358+
}
359+
300360
private void assertContextForWebTests(WebApplicationContext wac) throws Exception {
301361
assertThat(wac.getEnvironment().getProperty("test.engine")).as("Environment").isNotNull();
302362

@@ -364,10 +424,19 @@ private List<Mapping> processAheadOfTime(TestContextAotGenerator generator, Set<
364424
testClasses.forEach(testClass -> {
365425
DefaultGenerationContext generationContext = generator.createGenerationContext(testClass);
366426
MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass);
367-
ClassName className = generator.processAheadOfTime(mergedConfig, generationContext);
368-
assertThat(className).isNotNull();
369-
mappings.add(new Mapping(mergedConfig, className));
370-
generationContext.writeGeneratedContent();
427+
try {
428+
// Emulate AbstractAotProcessor.process().
429+
System.setProperty(AbstractAotProcessor.AOT_PROCESSING, "true");
430+
431+
ClassName className = generator.processAheadOfTime(mergedConfig, generationContext);
432+
assertThat(className).isNotNull();
433+
mappings.add(new Mapping(mergedConfig, className));
434+
generationContext.writeGeneratedContent();
435+
}
436+
finally {
437+
// Emulate AbstractAotProcessor.process().
438+
System.clearProperty(AbstractAotProcessor.AOT_PROCESSING);
439+
}
371440
});
372441
return mappings;
373442
}
@@ -422,29 +491,43 @@ record Mapping(MergedContextConfiguration mergedConfig, ClassName className) {
422491
"org/springframework/test/context/aot/samples/basic/BasicSpringVintageTests__TestContext004_BeanFactoryRegistrations.java",
423492
"org/springframework/test/context/aot/samples/basic/BasicTestConfiguration__TestContext004_BeanDefinitions.java",
424493
"org/springframework/test/context/support/DynamicPropertyRegistrarBeanInitializer__TestContext004_BeanDefinitions.java",
425-
// SqlScriptsSpringJupiterTests
494+
// EasyMockBeanJupiterTests
426495
"org/springframework/context/event/DefaultEventListenerFactory__TestContext005_BeanDefinitions.java",
427496
"org/springframework/context/event/EventListenerMethodProcessor__TestContext005_BeanDefinitions.java",
428-
"org/springframework/test/context/aot/samples/jdbc/SqlScriptsSpringJupiterTests__TestContext005_ApplicationContextInitializer.java",
429-
"org/springframework/test/context/aot/samples/jdbc/SqlScriptsSpringJupiterTests__TestContext005_BeanFactoryRegistrations.java",
430-
"org/springframework/test/context/jdbc/EmptyDatabaseConfig__TestContext005_BeanDefinitions.java",
497+
"org/springframework/test/context/aot/samples/bean/override/EasyMockBeanJupiterTests__TestContext005_ApplicationContextInitializer.java",
498+
"org/springframework/test/context/aot/samples/bean/override/EasyMockBeanJupiterTests__TestContext005_BeanDefinitions.java",
499+
"org/springframework/test/context/aot/samples/bean/override/EasyMockBeanJupiterTests__TestContext005_BeanFactoryRegistrations.java",
431500
"org/springframework/test/context/support/DynamicPropertyRegistrarBeanInitializer__TestContext005_BeanDefinitions.java",
432-
// WebSpringJupiterTests
501+
// MockitoBeanJupiterTests
433502
"org/springframework/context/event/DefaultEventListenerFactory__TestContext006_BeanDefinitions.java",
434503
"org/springframework/context/event/EventListenerMethodProcessor__TestContext006_BeanDefinitions.java",
435-
"org/springframework/test/context/aot/samples/web/WebSpringJupiterTests__TestContext006_ApplicationContextInitializer.java",
436-
"org/springframework/test/context/aot/samples/web/WebSpringJupiterTests__TestContext006_BeanFactoryRegistrations.java",
437-
"org/springframework/test/context/aot/samples/web/WebTestConfiguration__TestContext006_BeanDefinitions.java",
438-
"org/springframework/web/servlet/config/annotation/DelegatingWebMvcConfiguration__TestContext006_Autowiring.java",
439-
"org/springframework/web/servlet/config/annotation/DelegatingWebMvcConfiguration__TestContext006_BeanDefinitions.java",
504+
"org/springframework/test/context/aot/samples/bean/override/MockitoBeanJupiterTests__TestContext006_ApplicationContextInitializer.java",
505+
"org/springframework/test/context/aot/samples/bean/override/MockitoBeanJupiterTests__TestContext006_BeanDefinitions.java",
506+
"org/springframework/test/context/aot/samples/bean/override/MockitoBeanJupiterTests__TestContext006_BeanFactoryRegistrations.java",
440507
"org/springframework/test/context/support/DynamicPropertyRegistrarBeanInitializer__TestContext006_BeanDefinitions.java",
441-
// XmlSpringJupiterTests
508+
// SqlScriptsSpringJupiterTests
442509
"org/springframework/context/event/DefaultEventListenerFactory__TestContext007_BeanDefinitions.java",
443510
"org/springframework/context/event/EventListenerMethodProcessor__TestContext007_BeanDefinitions.java",
444-
"org/springframework/test/context/aot/samples/common/DefaultMessageService__TestContext007_BeanDefinitions.java",
445-
"org/springframework/test/context/aot/samples/xml/XmlSpringJupiterTests__TestContext007_ApplicationContextInitializer.java",
446-
"org/springframework/test/context/aot/samples/xml/XmlSpringJupiterTests__TestContext007_BeanFactoryRegistrations.java",
511+
"org/springframework/test/context/aot/samples/jdbc/SqlScriptsSpringJupiterTests__TestContext007_ApplicationContextInitializer.java",
512+
"org/springframework/test/context/aot/samples/jdbc/SqlScriptsSpringJupiterTests__TestContext007_BeanFactoryRegistrations.java",
513+
"org/springframework/test/context/jdbc/EmptyDatabaseConfig__TestContext007_BeanDefinitions.java",
447514
"org/springframework/test/context/support/DynamicPropertyRegistrarBeanInitializer__TestContext007_BeanDefinitions.java",
515+
// WebSpringJupiterTests
516+
"org/springframework/context/event/DefaultEventListenerFactory__TestContext008_BeanDefinitions.java",
517+
"org/springframework/context/event/EventListenerMethodProcessor__TestContext008_BeanDefinitions.java",
518+
"org/springframework/test/context/aot/samples/web/WebSpringJupiterTests__TestContext008_ApplicationContextInitializer.java",
519+
"org/springframework/test/context/aot/samples/web/WebSpringJupiterTests__TestContext008_BeanFactoryRegistrations.java",
520+
"org/springframework/test/context/aot/samples/web/WebTestConfiguration__TestContext008_BeanDefinitions.java",
521+
"org/springframework/test/context/support/DynamicPropertyRegistrarBeanInitializer__TestContext008_BeanDefinitions.java",
522+
"org/springframework/web/servlet/config/annotation/DelegatingWebMvcConfiguration__TestContext008_Autowiring.java",
523+
"org/springframework/web/servlet/config/annotation/DelegatingWebMvcConfiguration__TestContext008_BeanDefinitions.java",
524+
// XmlSpringJupiterTests
525+
"org/springframework/context/event/DefaultEventListenerFactory__TestContext009_BeanDefinitions.java",
526+
"org/springframework/context/event/EventListenerMethodProcessor__TestContext009_BeanDefinitions.java",
527+
"org/springframework/test/context/aot/samples/common/DefaultMessageService__TestContext009_BeanDefinitions.java",
528+
"org/springframework/test/context/aot/samples/xml/XmlSpringJupiterTests__TestContext009_ApplicationContextInitializer.java",
529+
"org/springframework/test/context/aot/samples/xml/XmlSpringJupiterTests__TestContext009_BeanFactoryRegistrations.java",
530+
"org/springframework/test/context/support/DynamicPropertyRegistrarBeanInitializer__TestContext009_BeanDefinitions.java",
448531
};
449532

450533
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
/*
2+
* Copyright 2002-2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.test.context.aot.samples.bean.override;
18+
19+
import org.junit.jupiter.api.BeforeEach;
20+
import org.junit.jupiter.api.Test;
21+
22+
import org.springframework.beans.factory.annotation.Autowired;
23+
import org.springframework.context.annotation.Bean;
24+
import org.springframework.context.annotation.Configuration;
25+
import org.springframework.test.context.aot.samples.common.GreetingService;
26+
import org.springframework.test.context.aot.samples.common.MessageService;
27+
import org.springframework.test.context.bean.override.easymock.EasyMockBean;
28+
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
29+
30+
import static org.assertj.core.api.Assertions.assertThat;
31+
import static org.easymock.EasyMock.expect;
32+
import static org.easymock.EasyMock.replay;
33+
34+
/**
35+
* @author Sam Brannen
36+
* @since 6.2
37+
*/
38+
@SpringJUnitConfig
39+
public class EasyMockBeanJupiterTests {
40+
41+
/**
42+
* Mock for nonexistent bean.
43+
*/
44+
@EasyMockBean
45+
GreetingService greetingService;
46+
47+
/**
48+
* Mock for existing bean.
49+
*/
50+
@EasyMockBean
51+
MessageService messageService;
52+
53+
@BeforeEach
54+
void configureMocks(@Autowired GreetingService greetingService, @Autowired MessageService messageService) {
55+
expect(greetingService.greeting()).andReturn("enigma");
56+
expect(messageService.generateMessage()).andReturn("override");
57+
replay(greetingService, messageService);
58+
}
59+
60+
@Test
61+
void test() {
62+
assertThat(greetingService.greeting()).isEqualTo("enigma");
63+
assertThat(messageService.generateMessage()).isEqualTo("override");
64+
}
65+
66+
@Configuration(proxyBeanMethods = false)
67+
static class Config {
68+
69+
@Bean
70+
MessageService messageService() {
71+
return () -> "prod";
72+
}
73+
}
74+
75+
}
Lines changed: 28 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,42 +14,59 @@
1414
* limitations under the License.
1515
*/
1616

17-
package org.springframework.test.context.aot.samples.bean.override.convention;
17+
package org.springframework.test.context.aot.samples.bean.override;
1818

19+
import org.junit.jupiter.api.BeforeEach;
1920
import org.junit.jupiter.api.Test;
2021

22+
import org.springframework.beans.factory.annotation.Autowired;
2123
import org.springframework.context.annotation.Bean;
2224
import org.springframework.context.annotation.Configuration;
23-
import org.springframework.test.context.bean.override.convention.TestBean;
25+
import org.springframework.test.context.aot.samples.common.GreetingService;
26+
import org.springframework.test.context.aot.samples.common.MessageService;
27+
import org.springframework.test.context.bean.override.mockito.MockitoBean;
2428
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
2529

2630
import static org.assertj.core.api.Assertions.assertThat;
31+
import static org.mockito.BDDMockito.when;
2732

2833
/**
2934
* @author Sam Brannen
3035
* @since 6.2
3136
*/
3237
@SpringJUnitConfig
33-
public class TestBeanJupiterTests {
38+
public class MockitoBeanJupiterTests {
3439

35-
@TestBean
36-
String magicBean;
40+
/**
41+
* Mock for nonexistent bean.
42+
*/
43+
@MockitoBean(enforceOverride = false)
44+
GreetingService greetingService;
3745

38-
static String magicBean() {
39-
return "enigma-override";
46+
/**
47+
* Mock for existing bean.
48+
*/
49+
@MockitoBean
50+
MessageService messageService;
51+
52+
@BeforeEach
53+
void configureMocks(@Autowired GreetingService greetingService, @Autowired MessageService messageService) {
54+
when(greetingService.greeting()).thenReturn("enigma");
55+
when(messageService.generateMessage()).thenReturn("override");
4056
}
4157

4258
@Test
43-
void tests() {
44-
assertThat(magicBean).isEqualTo("enigma-override");
59+
void test() {
60+
assertThat(greetingService.greeting()).isEqualTo("enigma");
61+
assertThat(messageService.generateMessage()).isEqualTo("override");
4562
}
4663

4764
@Configuration(proxyBeanMethods = false)
4865
static class Config {
4966

5067
@Bean
51-
String magicBean() {
52-
return "enigma";
68+
MessageService messageService() {
69+
return () -> "prod";
5370
}
5471
}
5572

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
* Copyright 2002-2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.test.context.aot.samples.common;
18+
19+
/**
20+
* @author Sam Brannen
21+
* @since 6.2
22+
*/
23+
@FunctionalInterface
24+
public interface GreetingService {
25+
26+
String greeting();
27+
28+
}

0 commit comments

Comments
 (0)