Skip to content

Commit 08a789c

Browse files
committed
Honor @⁠Fallback semantics for Test Bean Overrides
Closes gh-33924
1 parent 7a6e401 commit 08a789c

File tree

2 files changed

+108
-0
lines changed

2 files changed

+108
-0
lines changed

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

+22
Original file line numberDiff line numberDiff line change
@@ -331,6 +331,12 @@ private Set<String> getExistingBeanNamesByType(ConfigurableListableBeanFactory b
331331
return beanNames;
332332
}
333333

334+
/**
335+
* Determine the primary candidate in the given set of bean names.
336+
* <p>Honors both <em>primary</em> and <em>fallback</em> semantics.
337+
* @return the name of the primary candidate, or {@code null} if none found
338+
* @see org.springframework.beans.factory.support.DefaultListableBeanFactory#determinePrimaryCandidate(Map, Class)
339+
*/
334340
@Nullable
335341
private static String determinePrimaryCandidate(
336342
ConfigurableListableBeanFactory beanFactory, Set<String> candidateBeanNames, Class<?> beanType) {
@@ -340,6 +346,7 @@ private static String determinePrimaryCandidate(
340346
}
341347

342348
String primaryBeanName = null;
349+
// First pass: identify unique primary candidate
343350
for (String candidateBeanName : candidateBeanNames) {
344351
if (beanFactory.containsBeanDefinition(candidateBeanName)) {
345352
BeanDefinition beanDefinition = beanFactory.getBeanDefinition(candidateBeanName);
@@ -352,6 +359,21 @@ private static String determinePrimaryCandidate(
352359
}
353360
}
354361
}
362+
// Second pass: identify unique non-fallback candidate
363+
if (primaryBeanName == null) {
364+
for (String candidateBeanName : candidateBeanNames) {
365+
if (beanFactory.containsBeanDefinition(candidateBeanName)) {
366+
BeanDefinition beanDefinition = beanFactory.getBeanDefinition(candidateBeanName);
367+
if (!beanDefinition.isFallback()) {
368+
if (primaryBeanName != null) {
369+
// More than one non-fallback bean found among candidates.
370+
return null;
371+
}
372+
primaryBeanName = candidateBeanName;
373+
}
374+
}
375+
}
376+
}
355377
return primaryBeanName;
356378
}
357379

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
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.bean.override.convention;
18+
19+
import java.util.List;
20+
21+
import org.junit.jupiter.api.Test;
22+
import org.junit.jupiter.api.extension.ExtendWith;
23+
24+
import org.springframework.beans.factory.annotation.Autowired;
25+
import org.springframework.context.annotation.Bean;
26+
import org.springframework.context.annotation.Configuration;
27+
import org.springframework.context.annotation.Fallback;
28+
import org.springframework.test.annotation.DirtiesContext;
29+
import org.springframework.test.context.bean.override.example.ExampleService;
30+
import org.springframework.test.context.junit.jupiter.SpringExtension;
31+
32+
import static org.assertj.core.api.Assertions.assertThat;
33+
34+
/**
35+
* Verifies that {@link TestBean @TestBean} can be used to override a bean by-type
36+
* when there are multiple candidates and only one that is not a fallback.
37+
*
38+
* @author Sam Brannen
39+
* @since 6.2.1
40+
*/
41+
@ExtendWith(SpringExtension.class)
42+
@DirtiesContext
43+
class TestBeanWithMultipleExistingBeansAndOneNonFallbackIntegrationTests {
44+
45+
@TestBean
46+
ExampleService service;
47+
48+
@Autowired
49+
List<ExampleService> services;
50+
51+
52+
static ExampleService service() {
53+
return () -> "overridden";
54+
}
55+
56+
57+
@Test
58+
void test() {
59+
assertThat(service.greeting()).isEqualTo("overridden");
60+
assertThat(services).extracting(ExampleService::greeting)
61+
.containsExactlyInAnyOrder("overridden", "two", "three");
62+
}
63+
64+
65+
@Configuration(proxyBeanMethods = false)
66+
static class Config {
67+
68+
@Bean
69+
ExampleService one() {
70+
return () -> "one";
71+
}
72+
73+
@Bean
74+
@Fallback
75+
ExampleService two() {
76+
return () -> "two";
77+
}
78+
79+
@Bean
80+
@Fallback
81+
ExampleService three() {
82+
return () -> "three";
83+
}
84+
}
85+
86+
}

0 commit comments

Comments
 (0)