Skip to content

Commit 2d4baec

Browse files
committed
Merge branch '6.2.x'
2 parents 83517d0 + 54948a4 commit 2d4baec

File tree

5 files changed

+252
-17
lines changed

5 files changed

+252
-17
lines changed

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

+29-3
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,13 @@
1717
package org.springframework.test.context.bean.override;
1818

1919
import java.lang.reflect.Field;
20-
import java.util.HashMap;
20+
import java.util.LinkedHashMap;
21+
import java.util.List;
2122
import java.util.Map;
23+
import java.util.Map.Entry;
24+
25+
import org.apache.commons.logging.Log;
26+
import org.apache.commons.logging.LogFactory;
2227

2328
import org.springframework.beans.factory.BeanCreationException;
2429
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
@@ -37,9 +42,12 @@
3742
*/
3843
class BeanOverrideRegistry {
3944

40-
private final Map<BeanOverrideHandler, String> handlerToBeanNameMap = new HashMap<>();
45+
private static final Log logger = LogFactory.getLog(BeanOverrideRegistry.class);
46+
4147

42-
private final Map<String, BeanOverrideHandler> wrappingBeanOverrideHandlers = new HashMap<>();
48+
private final Map<BeanOverrideHandler, String> handlerToBeanNameMap = new LinkedHashMap<>();
49+
50+
private final Map<String, BeanOverrideHandler> wrappingBeanOverrideHandlers = new LinkedHashMap<>();
4351

4452
private final ConfigurableBeanFactory beanFactory;
4553

@@ -57,7 +65,25 @@ class BeanOverrideRegistry {
5765
* bean via {@link #wrapBeanIfNecessary(Object, String)}.
5866
*/
5967
void registerBeanOverrideHandler(BeanOverrideHandler handler, String beanName) {
68+
Assert.state(!this.handlerToBeanNameMap.containsKey(handler), () ->
69+
"Cannot register BeanOverrideHandler for bean with name '%s'; detected multiple registrations for %s"
70+
.formatted(beanName, handler));
71+
72+
// Check if beanName was already registered, before adding the new mapping.
73+
boolean beanNameAlreadyRegistered = this.handlerToBeanNameMap.containsValue(beanName);
74+
// Add new mapping before potentially logging a warning, to ensure that
75+
// the current handler is logged as well.
6076
this.handlerToBeanNameMap.put(handler, beanName);
77+
78+
if (beanNameAlreadyRegistered && logger.isWarnEnabled()) {
79+
List<BeanOverrideHandler> competingHandlers = this.handlerToBeanNameMap.entrySet().stream()
80+
.filter(entry -> entry.getValue().equals(beanName))
81+
.map(Entry::getKey)
82+
.toList();
83+
logger.warn("Bean with name '%s' was overridden by multiple handlers: %s"
84+
.formatted(beanName, competingHandlers));
85+
}
86+
6187
if (handler.getStrategy() == BeanOverrideStrategy.WRAP) {
6288
this.wrappingBeanOverrideHandlers.put(beanName, handler);
6389
}
Original file line numberDiff line numberDiff line change
@@ -25,34 +25,37 @@
2525
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
2626

2727
import static org.assertj.core.api.Assertions.assertThat;
28+
import static org.springframework.test.mockito.MockitoAssertions.assertIsMock;
2829

2930
/**
3031
* Integration tests for {@link MockitoBean @MockitoBean} where duplicate mocks
31-
* are created for the same nonexistent type.
32+
* are created for the same nonexistent type, selected by-type.
3233
*
3334
* @author Sam Brannen
3435
* @since 6.2.1
3536
* @see <a href="https://github.com/spring-projects/spring-framework/issues/34025">gh-34025</a>
37+
* @see MockitoBeanDuplicateTypeReplacementIntegrationTests
3638
* @see MockitoSpyBeanDuplicateTypeIntegrationTests
37-
* @see MockitoSpyBeanDuplicateTypeAndNameIntegrationTests
3839
*/
3940
@SpringJUnitConfig
40-
public class MockitoBeanDuplicateTypeIntegrationTests {
41+
public class MockitoBeanDuplicateTypeCreationIntegrationTests {
4142

4243
@MockitoBean
43-
ExampleService service1;
44+
ExampleService mock1;
4445

4546
@MockitoBean
46-
ExampleService service2;
47+
ExampleService mock2;
4748

4849
@Autowired
4950
List<ExampleService> services;
5051

5152

5253
@Test
5354
void duplicateMocksShouldHaveBeenCreated() {
54-
assertThat(service1).isNotSameAs(service2);
55-
assertThat(services).hasSize(2);
55+
assertThat(services).containsExactly(mock1, mock2);
56+
assertThat(mock1).isNotSameAs(mock2);
57+
assertIsMock(mock1);
58+
assertIsMock(mock2);
5659
}
5760

5861
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
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.mockito;
18+
19+
import java.util.List;
20+
21+
import org.junit.jupiter.api.Test;
22+
23+
import org.springframework.beans.factory.annotation.Autowired;
24+
import org.springframework.context.annotation.Bean;
25+
import org.springframework.context.annotation.Configuration;
26+
import org.springframework.test.context.bean.override.example.ExampleService;
27+
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
28+
29+
import static org.assertj.core.api.Assertions.assertThat;
30+
import static org.mockito.BDDMockito.given;
31+
import static org.springframework.test.context.bean.override.mockito.MockReset.AFTER;
32+
import static org.springframework.test.context.bean.override.mockito.MockReset.BEFORE;
33+
import static org.springframework.test.mockito.MockitoAssertions.assertIsMock;
34+
35+
/**
36+
* Integration tests for {@link MockitoBean @MockitoBean} where duplicate mocks
37+
* are created to replace the same existing bean, selected by-type.
38+
*
39+
* <p>In other words, this test class demonstrates how one {@code @MockitoBean}
40+
* can silently override another {@code @MockitoBean}.
41+
*
42+
* @author Sam Brannen
43+
* @since 6.2.1
44+
* @see <a href="https://github.com/spring-projects/spring-framework/issues/34056">gh-34056</a>
45+
* @see MockitoBeanDuplicateTypeCreationIntegrationTests
46+
* @see MockitoSpyBeanDuplicateTypeIntegrationTests
47+
*/
48+
@SpringJUnitConfig
49+
public class MockitoBeanDuplicateTypeReplacementIntegrationTests {
50+
51+
@MockitoBean(reset = AFTER)
52+
ExampleService mock1;
53+
54+
@MockitoBean(reset = BEFORE)
55+
ExampleService mock2;
56+
57+
@Autowired
58+
List<ExampleService> services;
59+
60+
/**
61+
* One could argue that we would ideally expect an exception to be thrown when
62+
* two competing mocks are created to replace the same existing bean; however,
63+
* we currently only log a warning in such cases.
64+
* <p>This method therefore asserts the status quo in terms of behavior.
65+
* <p>And the log can be manually checked to verify that an appropriate
66+
* warning was logged.
67+
*/
68+
@Test
69+
void onlyOneMockShouldHaveBeenCreated() {
70+
// Currently logs something similar to the following.
71+
//
72+
// WARN - Bean with name 'exampleService' was overridden by multiple handlers:
73+
// [MockitoBeanOverrideHandler@5478ce1e ..., MockitoBeanOverrideHandler@5edc70ed ...]
74+
75+
// Last one wins: there's only one physical mock
76+
assertThat(services).containsExactly(mock2);
77+
assertThat(mock1).isSameAs(mock2);
78+
79+
assertIsMock(mock2);
80+
assertThat(MockReset.get(mock2)).as("MockReset").isEqualTo(BEFORE);
81+
82+
assertThat(mock2.greeting()).isNull();
83+
given(mock2.greeting()).willReturn("mocked");
84+
assertThat(mock2.greeting()).isEqualTo("mocked");
85+
}
86+
87+
88+
@Configuration
89+
static class Config {
90+
91+
@Bean
92+
ExampleService exampleService() {
93+
return () -> "@Bean";
94+
}
95+
}
96+
97+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
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.mockito;
18+
19+
import java.util.List;
20+
21+
import org.junit.jupiter.api.Test;
22+
23+
import org.springframework.beans.factory.annotation.Autowired;
24+
import org.springframework.context.annotation.Bean;
25+
import org.springframework.context.annotation.Configuration;
26+
import org.springframework.test.context.bean.override.convention.TestBean;
27+
import org.springframework.test.context.bean.override.example.ExampleService;
28+
import org.springframework.test.context.bean.override.example.RealExampleService;
29+
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
30+
31+
import static org.assertj.core.api.Assertions.assertThat;
32+
import static org.mockito.BDDMockito.given;
33+
import static org.springframework.test.mockito.MockitoAssertions.assertIsMock;
34+
35+
/**
36+
* Integration tests for Bean Overrides where a {@link MockitoBean @MockitoBean}
37+
* overrides a {@link TestBean @TestBean} when trying to replace the same existing
38+
* bean, selected by-type.
39+
*
40+
* <p>In other words, this test class demonstrates how one Bean Override can
41+
* silently override another Bean Override.
42+
*
43+
* @author Sam Brannen
44+
* @since 6.2.1
45+
* @see <a href="https://github.com/spring-projects/spring-framework/issues/34056">gh-34056</a>
46+
* @see MockitoBeanDuplicateTypeCreationIntegrationTests
47+
* @see MockitoBeanDuplicateTypeReplacementIntegrationTests
48+
*/
49+
@SpringJUnitConfig
50+
public class MockitoBeanOverridesTestBeanIntegrationTests {
51+
52+
@TestBean
53+
ExampleService testService;
54+
55+
@MockitoBean
56+
ExampleService mockService;
57+
58+
@Autowired
59+
List<ExampleService> services;
60+
61+
62+
static ExampleService testService() {
63+
return new RealExampleService("@TestBean");
64+
}
65+
66+
67+
/**
68+
* One could argue that we would ideally expect an exception to be thrown when
69+
* two competing overrides are created to replace the same existing bean; however,
70+
* we currently only log a warning in such cases.
71+
* <p>This method therefore asserts the status quo in terms of behavior.
72+
* <p>And the log can be manually checked to verify that an appropriate
73+
* warning was logged.
74+
*/
75+
@Test
76+
void mockitoBeanShouldOverrideTestBean() {
77+
// Currently logs something similar to the following.
78+
//
79+
// WARN - Bean with name 'exampleService' was overridden by multiple handlers:
80+
// [TestBeanOverrideHandler@770beef5 ..., MockitoBeanOverrideHandler@6dd1f638 ...]
81+
82+
// Last override wins...
83+
assertThat(services).containsExactly(mockService);
84+
assertThat(testService).isSameAs(mockService);
85+
86+
assertIsMock(mockService);
87+
88+
assertThat(mockService.greeting()).isNull();
89+
given(mockService.greeting()).willReturn("mocked");
90+
assertThat(mockService.greeting()).isEqualTo("mocked");
91+
}
92+
93+
94+
@Configuration
95+
static class Config {
96+
97+
@Bean
98+
ExampleService exampleService() {
99+
return () -> "@Bean";
100+
}
101+
}
102+
103+
}

spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoSpyBeanDuplicateTypeIntegrationTests.java

+13-7
Original file line numberDiff line numberDiff line change
@@ -36,28 +36,34 @@
3636
*
3737
* @author Sam Brannen
3838
* @since 6.2.1
39-
* @see MockitoBeanDuplicateTypeIntegrationTests
39+
* @see <a href="https://github.com/spring-projects/spring-framework/issues/34056">gh-34056</a>
40+
* @see MockitoBeanDuplicateTypeCreationIntegrationTests
4041
* @see MockitoSpyBeanDuplicateTypeAndNameIntegrationTests
4142
*/
4243
@SpringJUnitConfig
4344
public class MockitoSpyBeanDuplicateTypeIntegrationTests {
4445

4546
@MockitoSpyBean
46-
ExampleService service1;
47+
ExampleService spy1;
4748

4849
@MockitoSpyBean
49-
ExampleService service2;
50+
ExampleService spy2;
5051

5152
@Autowired
5253
List<ExampleService> services;
5354

5455

5556
@Test
56-
void test() {
57-
assertThat(service1).isSameAs(service2);
58-
assertThat(services).containsExactly(service1);
57+
void onlyOneSpyShouldHaveBeenCreated() {
58+
// Currently logs something similar to the following.
59+
//
60+
// WARN - Bean with name 'exampleService' was overridden by multiple handlers:
61+
// [MockitoSpyBeanOverrideHandler@1d269ed7 ..., MockitoSpyBeanOverrideHandler@437ebf59 ...]
5962

60-
assertIsSpy(service1, "service1");
63+
assertThat(services).containsExactly(spy2);
64+
assertThat(spy1).isSameAs(spy2);
65+
66+
assertIsSpy(spy2);
6167
}
6268

6369

0 commit comments

Comments
 (0)