Skip to content

Commit f8e7bf7

Browse files
committed
Merge branch '6.2.x'
2 parents 67f3ff5 + e8f873a commit f8e7bf7

5 files changed

+211
-10
lines changed

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

+10-8
Original file line numberDiff line numberDiff line change
@@ -183,30 +183,32 @@ private static List<BeanOverrideHandler> findHandlers(Class<?> testClass, boolea
183183
* @param testClass the original test class
184184
* @param handlers the list of handlers found
185185
* @param localFieldsOnly whether to search only on local fields within the type hierarchy
186-
* @param visitedEnclosingClasses the set of enclosing classes already visited
186+
* @param visitedTypes the set of types already visited
187187
* @since 6.2.2
188188
*/
189189
private static void findHandlers(Class<?> clazz, Class<?> testClass, List<BeanOverrideHandler> handlers,
190-
boolean localFieldsOnly, Set<Class<?>> visitedEnclosingClasses) {
190+
boolean localFieldsOnly, Set<Class<?>> visitedTypes) {
191+
192+
// 0) Ensure that we do not process the same class or interface multiple times.
193+
if (!visitedTypes.add(clazz)) {
194+
return;
195+
}
191196

192197
// 1) Search enclosing class hierarchy.
193198
if (!localFieldsOnly && TestContextAnnotationUtils.searchEnclosingClass(clazz)) {
194-
Class<?> enclosingClass = clazz.getEnclosingClass();
195-
if (visitedEnclosingClasses.add(enclosingClass)) {
196-
findHandlers(enclosingClass, testClass, handlers, localFieldsOnly, visitedEnclosingClasses);
197-
}
199+
findHandlers(clazz.getEnclosingClass(), testClass, handlers, localFieldsOnly, visitedTypes);
198200
}
199201

200202
// 2) Search class hierarchy.
201203
Class<?> superclass = clazz.getSuperclass();
202204
if (superclass != null && superclass != Object.class) {
203-
findHandlers(superclass, testClass, handlers, localFieldsOnly, visitedEnclosingClasses);
205+
findHandlers(superclass, testClass, handlers, localFieldsOnly, visitedTypes);
204206
}
205207

206208
if (!localFieldsOnly) {
207209
// 3) Search interfaces.
208210
for (Class<?> ifc : clazz.getInterfaces()) {
209-
findHandlers(ifc, testClass, handlers, localFieldsOnly, visitedEnclosingClasses);
211+
findHandlers(ifc, testClass, handlers, localFieldsOnly, visitedTypes);
210212
}
211213

212214
// 4) Process current class.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
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;
18+
19+
import org.junit.jupiter.api.Test;
20+
import org.junit.jupiter.api.extension.ExtendWith;
21+
22+
import org.springframework.beans.factory.annotation.Autowired;
23+
import org.springframework.context.ApplicationContext;
24+
import org.springframework.test.context.bean.override.example.ExampleService;
25+
import org.springframework.test.context.junit.jupiter.SpringExtension;
26+
27+
import static org.assertj.core.api.Assertions.assertThat;
28+
import static org.springframework.test.mockito.MockitoAssertions.assertIsMock;
29+
30+
/**
31+
* Abstract top-level class and abstract inner class for integration tests for
32+
* {@link MockitoBean @MockitoBean} which verify that {@code @MockitoBean} fields
33+
* are not discovered more than once when searching intertwined enclosing class
34+
* hierarchies and type hierarchies, when a superclass is <em>present</em> twice
35+
* in the intertwined hierarchies.
36+
*
37+
* @author Sam Brannen
38+
* @since 6.2.7
39+
* @see MockitoBeanNestedAndTypeHierarchiesWithSuperclassPresentTwiceTests
40+
* @see <a href="https://github.com/spring-projects/spring-framework/issues/34844">gh-34844</a>
41+
*/
42+
@ExtendWith(SpringExtension.class)
43+
abstract class AbstractMockitoBeanNestedAndTypeHierarchiesWithSuperclassPresentTwiceTests {
44+
45+
@Autowired
46+
ApplicationContext enclosingContext;
47+
48+
@MockitoBean
49+
ExampleService service;
50+
51+
52+
@Test
53+
void topLevelTest() {
54+
assertIsMock(service);
55+
assertThat(enclosingContext.getBeanNamesForType(ExampleService.class)).hasSize(1);
56+
}
57+
58+
59+
abstract class AbstractBaseClassForNestedTests {
60+
61+
@Test
62+
void nestedTest(ApplicationContext nestedContext) {
63+
assertIsMock(service);
64+
assertThat(enclosingContext).isSameAs(nestedContext);
65+
assertThat(enclosingContext.getBeanNamesForType(ExampleService.class)).hasSize(1);
66+
}
67+
}
68+
69+
}

spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoBeanNestedAndTypeHierarchiesTests.java renamed to spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoBeanNestedAndTypeHierarchiesWithEnclosingClassPresentTwiceTests.java

+7-2
Original file line numberDiff line numberDiff line change
@@ -31,14 +31,17 @@
3131
/**
3232
* Integration tests for {@link MockitoBean @MockitoBean} which verify that
3333
* {@code @MockitoBean} fields are not discovered more than once when searching
34-
* intertwined enclosing class hierarchies and type hierarchies.
34+
* intertwined enclosing class hierarchies and type hierarchies, when an enclosing
35+
* class is <em>present</em> twice in the intertwined hierarchies.
3536
*
3637
* @author Sam Brannen
3738
* @since 6.2.3
39+
* @see MockitoBeanNestedAndTypeHierarchiesWithSuperclassPresentTwiceTests
40+
* @see MockitoBeanWithInterfacePresentTwiceTests
3841
* @see <a href="https://github.com/spring-projects/spring-framework/issues/34324">gh-34324</a>
3942
*/
4043
@ExtendWith(SpringExtension.class)
41-
class MockitoBeanNestedAndTypeHierarchiesTests {
44+
class MockitoBeanNestedAndTypeHierarchiesWithEnclosingClassPresentTwiceTests {
4245

4346
@Autowired
4447
ApplicationContext enclosingContext;
@@ -50,6 +53,7 @@ class MockitoBeanNestedAndTypeHierarchiesTests {
5053
@Test
5154
void topLevelTest() {
5255
assertIsMock(service);
56+
assertThat(enclosingContext.getBeanNamesForType(ExampleService.class)).hasSize(1);
5357

5458
// The following are prerequisites for the reported regression.
5559
assertThat(NestedTests.class.getSuperclass())
@@ -66,6 +70,7 @@ abstract class AbstractBaseClassForNestedTests {
6670
void nestedTest(ApplicationContext nestedContext) {
6771
assertIsMock(service);
6872
assertThat(enclosingContext).isSameAs(nestedContext);
73+
assertThat(enclosingContext.getBeanNamesForType(ExampleService.class)).hasSize(1);
6974
}
7075
}
7176

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
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;
18+
19+
import org.junit.jupiter.api.Nested;
20+
import org.junit.jupiter.api.Test;
21+
22+
import static org.assertj.core.api.Assertions.assertThat;
23+
24+
/**
25+
* Integration tests for {@link MockitoBean @MockitoBean} which verify that
26+
* {@code @MockitoBean} fields are not discovered more than once when searching
27+
* intertwined enclosing class hierarchies and type hierarchies, when a superclass
28+
* is <em>present</em> twice in the intertwined hierarchies.
29+
*
30+
* @author Sam Brannen
31+
* @since 6.2.7
32+
* @see MockitoBeanNestedAndTypeHierarchiesWithEnclosingClassPresentTwiceTests
33+
* @see MockitoBeanWithInterfacePresentTwiceTests
34+
* @see <a href="https://github.com/spring-projects/spring-framework/issues/34844">gh-34844</a>
35+
*/
36+
class MockitoBeanNestedAndTypeHierarchiesWithSuperclassPresentTwiceTests
37+
extends AbstractMockitoBeanNestedAndTypeHierarchiesWithSuperclassPresentTwiceTests {
38+
39+
@Test
40+
@Override
41+
void topLevelTest() {
42+
super.topLevelTest();
43+
44+
// The following are prerequisites for the reported regression.
45+
assertThat(NestedTests.class.getSuperclass())
46+
.isEqualTo(AbstractBaseClassForNestedTests.class);
47+
assertThat(NestedTests.class.getEnclosingClass())
48+
.isEqualTo(getClass());
49+
assertThat(NestedTests.class.getEnclosingClass().getSuperclass())
50+
.isEqualTo(AbstractBaseClassForNestedTests.class.getEnclosingClass())
51+
.isEqualTo(getClass().getSuperclass());
52+
}
53+
54+
55+
@Nested
56+
class NestedTests extends AbstractBaseClassForNestedTests {
57+
}
58+
59+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
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;
18+
19+
import org.junit.jupiter.api.Test;
20+
import org.junit.jupiter.api.extension.ExtendWith;
21+
22+
import org.springframework.beans.factory.annotation.Autowired;
23+
import org.springframework.context.ApplicationContext;
24+
import org.springframework.test.context.bean.override.example.ExampleService;
25+
import org.springframework.test.context.junit.jupiter.SpringExtension;
26+
27+
import static org.assertj.core.api.Assertions.assertThat;
28+
import static org.springframework.test.mockito.MockitoAssertions.assertIsMock;
29+
30+
/**
31+
* Integration tests for {@link MockitoBean @MockitoBean} which verify that type-level
32+
* {@code @MockitoBean} declarations are not discovered more than once when searching
33+
* a type hierarchy, when an interface is <em>present</em> twice in the hierarchy.
34+
*
35+
* @author Sam Brannen
36+
* @since 6.2.7
37+
* @see MockitoBeanNestedAndTypeHierarchiesWithEnclosingClassPresentTwiceTests
38+
* @see MockitoBeanNestedAndTypeHierarchiesWithSuperclassPresentTwiceTests
39+
* @see <a href="https://github.com/spring-projects/spring-framework/issues/34844">gh-34844</a>
40+
*/
41+
class MockitoBeanWithInterfacePresentTwiceTests extends AbstractMockitoBeanWithInterfacePresentTwiceTests
42+
implements MockConfigInterface {
43+
44+
@Test
45+
void test(ApplicationContext context) {
46+
assertIsMock(service);
47+
assertThat(context.getBeanNamesForType(ExampleService.class)).hasSize(1);
48+
49+
// The following are prerequisites for the tested scenario.
50+
assertThat(getClass().getInterfaces()).containsExactly(MockConfigInterface.class);
51+
assertThat(getClass().getSuperclass().getInterfaces()).containsExactly(MockConfigInterface.class);
52+
}
53+
54+
}
55+
56+
@MockitoBean(types = ExampleService.class)
57+
interface MockConfigInterface {
58+
}
59+
60+
@ExtendWith(SpringExtension.class)
61+
abstract class AbstractMockitoBeanWithInterfacePresentTwiceTests implements MockConfigInterface {
62+
63+
@Autowired
64+
ExampleService service;
65+
66+
}

0 commit comments

Comments
 (0)