Skip to content

Commit 2fa7b30

Browse files
committed
Merge branch '6.2.x'
2 parents 7ab108a + 374c3b4 commit 2fa7b30

File tree

6 files changed

+447
-5
lines changed

6 files changed

+447
-5
lines changed

spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverrideBeanFactoryPostProcessor.java

+18-1
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@ private void replaceOrCreateBean(ConfigurableListableBeanFactory beanFactory, Be
153153
// an existing bean definition.
154154
if (beanFactory.containsBeanDefinition(beanName)) {
155155
existingBeanDefinition = beanFactory.getBeanDefinition(beanName);
156+
setQualifiedElement(existingBeanDefinition, handler);
156157
}
157158
}
158159
else {
@@ -167,6 +168,7 @@ private void replaceOrCreateBean(ConfigurableListableBeanFactory beanFactory, Be
167168
if (candidates.contains(beanName)) {
168169
// 3) We are overriding an existing bean by-name.
169170
existingBeanDefinition = beanFactory.getBeanDefinition(beanName);
171+
setQualifiedElement(existingBeanDefinition, handler);
170172
}
171173
else if (requireExistingBean) {
172174
Field field = handler.getField();
@@ -448,10 +450,25 @@ private static Set<String> getExistingBeanNamesByType(ConfigurableListableBeanFa
448450
private static RootBeanDefinition createPseudoBeanDefinition(BeanOverrideHandler handler) {
449451
RootBeanDefinition definition = new RootBeanDefinition(handler.getBeanType().resolve());
450452
definition.setTargetType(handler.getBeanType());
451-
definition.setQualifiedElement(handler.getField());
453+
setQualifiedElement(definition, handler);
452454
return definition;
453455
}
454456

457+
/**
458+
* Set the {@linkplain RootBeanDefinition#setQualifiedElement(java.lang.reflect.AnnotatedElement)
459+
* qualified element} in the supplied {@link BeanDefinition} to the
460+
* {@linkplain BeanOverrideHandler#getField() field} of the supplied
461+
* {@code BeanOverrideHandler}.
462+
* <p>This is necessary for proper autowiring candidate resolution.
463+
* @since 6.2.6
464+
*/
465+
private static void setQualifiedElement(BeanDefinition beanDefinition, BeanOverrideHandler handler) {
466+
Field field = handler.getField();
467+
if (field != null && beanDefinition instanceof RootBeanDefinition rbd) {
468+
rbd.setQualifiedElement(field);
469+
}
470+
}
471+
455472
/**
456473
* Validate that the {@link BeanDefinition} for the supplied bean name is suitable
457474
* for being replaced by a bean override.

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

+1-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2024 the original author or authors.
2+
* Copyright 2002-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -147,8 +147,6 @@ void endToEndTestsForEntireSpringTestModule() {
147147
.filter(clazz -> clazz.getSimpleName().endsWith("Tests"))
148148
// TestNG EJB tests use @PersistenceContext which is not yet supported in tests in AOT mode.
149149
.filter(clazz -> !clazz.getPackageName().contains("testng.transaction.ejb"))
150-
// Uncomment the following to disable Bean Override tests since they are not yet supported in AOT mode.
151-
// .filter(clazz -> !clazz.getPackageName().contains("test.context.bean.override"))
152150
.toList();
153151

154152
// Optionally set failOnError flag to true to halt processing at the first failure.
@@ -169,7 +167,6 @@ void endToEndTestsForBeanOverrides() {
169167
void endToEndTestsForSelectedTestClasses() {
170168
List<Class<?>> testClasses = List.of(
171169
org.springframework.test.context.bean.override.easymock.EasyMockBeanIntegrationTests.class,
172-
org.springframework.test.context.bean.override.mockito.MockitoBeanForByNameLookupIntegrationTests.class,
173170
org.springframework.test.context.junit4.SpringJUnit4ClassRunnerAppCtxTests.class,
174171
org.springframework.test.context.junit4.ParameterizedDependencyInjectionTests.class
175172
);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
/*
2+
* Copyright 2002-2025 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.bean.override.mockito.integration;
18+
19+
import java.lang.annotation.Retention;
20+
import java.lang.annotation.RetentionPolicy;
21+
22+
import org.junit.jupiter.api.Test;
23+
import org.junit.jupiter.api.extension.ExtendWith;
24+
25+
import org.springframework.beans.factory.annotation.Autowired;
26+
import org.springframework.beans.factory.annotation.Qualifier;
27+
import org.springframework.context.ApplicationContext;
28+
import org.springframework.context.annotation.Bean;
29+
import org.springframework.context.annotation.Configuration;
30+
import org.springframework.test.context.bean.override.example.ExampleService;
31+
import org.springframework.test.context.bean.override.example.ExampleServiceCaller;
32+
import org.springframework.test.context.bean.override.mockito.MockitoBean;
33+
import org.springframework.test.context.junit.jupiter.SpringExtension;
34+
35+
import static org.assertj.core.api.Assertions.assertThat;
36+
import static org.mockito.Mockito.when;
37+
import static org.springframework.test.mockito.MockitoAssertions.assertIsMock;
38+
import static org.springframework.test.mockito.MockitoAssertions.assertMockName;
39+
40+
/**
41+
* Tests for {@link MockitoBean @MockitoBean} where the mocked bean is associated
42+
* with a custom {@link Qualifier @Qualifier} annotation and the bean to override
43+
* is selected by name.
44+
*
45+
* @author Sam Brannen
46+
* @since 6.2.6
47+
* @see <a href="https://github.com/spring-projects/spring-framework/issues/34646">gh-34646</a>
48+
* @see MockitoBeanWithCustomQualifierAnnotationByTypeTests
49+
*/
50+
@ExtendWith(SpringExtension.class)
51+
class MockitoBeanWithCustomQualifierAnnotationByNameTests {
52+
53+
@MockitoBean(name = "qualifiedService", enforceOverride = true)
54+
@MyQualifier
55+
ExampleService service;
56+
57+
@Autowired
58+
ExampleServiceCaller caller;
59+
60+
61+
@Test
62+
void test(ApplicationContext context) {
63+
assertIsMock(service);
64+
assertMockName(service, "qualifiedService");
65+
assertThat(service).isNotInstanceOf(QualifiedService.class);
66+
67+
// Since the 'service' field's type is ExampleService, the QualifiedService
68+
// bean in the @Configuration class effectively gets removed from the context,
69+
// or rather it never gets created because we register an ExampleService as
70+
// a manual singleton in its place.
71+
assertThat(context.getBeanNamesForType(QualifiedService.class)).isEmpty();
72+
assertThat(context.getBeanNamesForType(ExampleService.class)).hasSize(1);
73+
assertThat(context.getBeanNamesForType(ExampleServiceCaller.class)).hasSize(1);
74+
75+
when(service.greeting()).thenReturn("mock!");
76+
assertThat(caller.sayGreeting()).isEqualTo("I say mock!");
77+
}
78+
79+
80+
@Configuration(proxyBeanMethods = false)
81+
static class Config {
82+
83+
@Bean
84+
QualifiedService qualifiedService() {
85+
return new QualifiedService();
86+
}
87+
88+
@Bean
89+
ExampleServiceCaller myServiceCaller(@MyQualifier ExampleService service) {
90+
return new ExampleServiceCaller(service);
91+
}
92+
}
93+
94+
@Qualifier
95+
@Retention(RetentionPolicy.RUNTIME)
96+
@interface MyQualifier {
97+
}
98+
99+
@MyQualifier
100+
static class QualifiedService implements ExampleService {
101+
102+
@Override
103+
public String greeting() {
104+
return "Qualified service";
105+
}
106+
}
107+
108+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
/*
2+
* Copyright 2002-2025 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.bean.override.mockito.integration;
18+
19+
import java.lang.annotation.Retention;
20+
import java.lang.annotation.RetentionPolicy;
21+
22+
import org.junit.jupiter.api.Test;
23+
import org.junit.jupiter.api.extension.ExtendWith;
24+
25+
import org.springframework.beans.factory.annotation.Autowired;
26+
import org.springframework.beans.factory.annotation.Qualifier;
27+
import org.springframework.context.ApplicationContext;
28+
import org.springframework.context.annotation.Bean;
29+
import org.springframework.context.annotation.Configuration;
30+
import org.springframework.test.context.bean.override.example.ExampleService;
31+
import org.springframework.test.context.bean.override.example.ExampleServiceCaller;
32+
import org.springframework.test.context.bean.override.mockito.MockitoBean;
33+
import org.springframework.test.context.junit.jupiter.SpringExtension;
34+
35+
import static org.assertj.core.api.Assertions.assertThat;
36+
import static org.mockito.Mockito.when;
37+
import static org.springframework.test.mockito.MockitoAssertions.assertIsMock;
38+
import static org.springframework.test.mockito.MockitoAssertions.assertMockName;
39+
40+
/**
41+
* Tests for {@link MockitoBean @MockitoBean} where the mocked bean is associated
42+
* with a custom {@link Qualifier @Qualifier} annotation and the bean to override
43+
* is selected by type.
44+
*
45+
* @author Sam Brannen
46+
* @since 6.2.6
47+
* @see <a href="https://github.com/spring-projects/spring-framework/issues/34646">gh-34646</a>
48+
* @see MockitoBeanWithCustomQualifierAnnotationByNameTests
49+
*/
50+
@ExtendWith(SpringExtension.class)
51+
class MockitoBeanWithCustomQualifierAnnotationByTypeTests {
52+
53+
@MockitoBean(enforceOverride = true)
54+
@MyQualifier
55+
ExampleService service;
56+
57+
@Autowired
58+
ExampleServiceCaller caller;
59+
60+
61+
@Test
62+
void test(ApplicationContext context) {
63+
assertIsMock(service);
64+
assertMockName(service, "qualifiedService");
65+
assertThat(service).isNotInstanceOf(QualifiedService.class);
66+
67+
// Since the 'service' field's type is ExampleService, the QualifiedService
68+
// bean in the @Configuration class effectively gets removed from the context,
69+
// or rather it never gets created because we register an ExampleService as
70+
// a manual singleton in its place.
71+
assertThat(context.getBeanNamesForType(QualifiedService.class)).isEmpty();
72+
assertThat(context.getBeanNamesForType(ExampleService.class)).hasSize(1);
73+
assertThat(context.getBeanNamesForType(ExampleServiceCaller.class)).hasSize(1);
74+
75+
when(service.greeting()).thenReturn("mock!");
76+
assertThat(caller.sayGreeting()).isEqualTo("I say mock!");
77+
}
78+
79+
80+
@Configuration(proxyBeanMethods = false)
81+
static class Config {
82+
83+
@Bean
84+
QualifiedService qualifiedService() {
85+
return new QualifiedService();
86+
}
87+
88+
@Bean
89+
ExampleServiceCaller myServiceCaller(@MyQualifier ExampleService service) {
90+
return new ExampleServiceCaller(service);
91+
}
92+
}
93+
94+
@Qualifier
95+
@Retention(RetentionPolicy.RUNTIME)
96+
@interface MyQualifier {
97+
}
98+
99+
@MyQualifier
100+
static class QualifiedService implements ExampleService {
101+
102+
@Override
103+
public String greeting() {
104+
return "Qualified service";
105+
}
106+
}
107+
108+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
/*
2+
* Copyright 2002-2025 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.bean.override.mockito.integration;
18+
19+
import java.lang.annotation.Retention;
20+
import java.lang.annotation.RetentionPolicy;
21+
22+
import org.junit.jupiter.api.Test;
23+
import org.junit.jupiter.api.extension.ExtendWith;
24+
25+
import org.springframework.beans.factory.annotation.Autowired;
26+
import org.springframework.beans.factory.annotation.Qualifier;
27+
import org.springframework.context.ApplicationContext;
28+
import org.springframework.context.annotation.Bean;
29+
import org.springframework.context.annotation.Configuration;
30+
import org.springframework.test.context.bean.override.example.ExampleService;
31+
import org.springframework.test.context.bean.override.example.ExampleServiceCaller;
32+
import org.springframework.test.context.bean.override.mockito.MockitoSpyBean;
33+
import org.springframework.test.context.junit.jupiter.SpringExtension;
34+
35+
import static org.assertj.core.api.Assertions.assertThat;
36+
import static org.mockito.Mockito.verify;
37+
import static org.mockito.Mockito.when;
38+
import static org.springframework.test.mockito.MockitoAssertions.assertIsSpy;
39+
import static org.springframework.test.mockito.MockitoAssertions.assertMockName;
40+
41+
/**
42+
* Tests for {@link MockitoSpyBean @MockitoSpyBean} where the mocked bean is associated
43+
* with a custom {@link Qualifier @Qualifier} annotation and the bean to override
44+
* is selected by name.
45+
*
46+
* @author Sam Brannen
47+
* @since 6.2.6
48+
* @see <a href="https://github.com/spring-projects/spring-framework/issues/34646">gh-34646</a>
49+
* @see MockitoSpyBeanWithCustomQualifierAnnotationByTypeTests
50+
*/
51+
@ExtendWith(SpringExtension.class)
52+
class MockitoSpyBeanWithCustomQualifierAnnotationByNameTests {
53+
54+
@MockitoSpyBean(name = "qualifiedService")
55+
@MyQualifier
56+
ExampleService service;
57+
58+
@Autowired
59+
ExampleServiceCaller caller;
60+
61+
62+
@Test
63+
void test(ApplicationContext context) {
64+
assertIsSpy(service);
65+
assertMockName(service, "qualifiedService");
66+
assertThat(service).isInstanceOf(QualifiedService.class);
67+
68+
assertThat(context.getBeanNamesForType(QualifiedService.class)).hasSize(1);
69+
assertThat(context.getBeanNamesForType(ExampleService.class)).hasSize(1);
70+
assertThat(context.getBeanNamesForType(ExampleServiceCaller.class)).hasSize(1);
71+
72+
when(service.greeting()).thenReturn("mock!");
73+
assertThat(caller.sayGreeting()).isEqualTo("I say mock!");
74+
verify(service).greeting();
75+
}
76+
77+
78+
@Configuration(proxyBeanMethods = false)
79+
static class Config {
80+
81+
@Bean
82+
QualifiedService qualifiedService() {
83+
return new QualifiedService();
84+
}
85+
86+
@Bean
87+
ExampleServiceCaller myServiceCaller(@MyQualifier ExampleService service) {
88+
return new ExampleServiceCaller(service);
89+
}
90+
}
91+
92+
@Qualifier
93+
@Retention(RetentionPolicy.RUNTIME)
94+
@interface MyQualifier {
95+
}
96+
97+
@MyQualifier
98+
static class QualifiedService implements ExampleService {
99+
100+
@Override
101+
public String greeting() {
102+
return "Qualified service";
103+
}
104+
}
105+
106+
}

0 commit comments

Comments
 (0)