Browse Source
It is currently possible for one Bean Override to override another
logically equivalent Bean Override.
For example, a @TestBean can override a @MockitoBean, and vice versa.
In fact, it's also possible for a @MockitoBean to override another
@MockitoBean, for a @TestBean to override a @TestBean, etc.
However, there may be viable use cases for one override overriding
another override. For example, one may have a need to spy on a bean
created by a @TestBean factory method.
In light of that, we do not prohibit one Bean Override from overriding
another Bean Override; however, with this commit we now log a warning
to help developers diagnose issues in case such an override is
unintentional.
For example, given the following test class, where TestConfig registers
a single bean of type MyService named "myService"...
@SpringJUnitConfig(TestConfig.class)
class MyTests {
@TestBean(methodName = "example.TestUtils#createMyService")
MyService testService;
@MockitoBean
MyService mockService;
@Test
void test() {
// ...
}
}
... running that test class results in a log message similar to the
following, which has been formatted for readability.
WARN - Bean with name 'myService' was overridden by multiple handlers:
[
[TestBeanOverrideHandler@44b21f9f
field = example.MyService example.MyTests.testService,
beanType = example.MyService,
beanName = [null],
strategy = REPLACE_OR_CREATE
],
[MockitoBeanOverrideHandler@7ee8130e
field = example.MyService example.MyTests.mockService,
beanType = example.MyService,
beanName = [null],
strategy = REPLACE_OR_CREATE,
reset = AFTER,
extraInterfaces = set[[empty]],
answers = RETURNS_DEFAULTS, serializable = false
]
]
NOTE: The last registered BeanOverrideHandler wins. In the above
example, that means that @MockitoBean overrides @TestBean, resulting
in a Mockito mock for the MyService bean in the test's
ApplicationContext.
Closes gh-34056
pull/34398/head
5 changed files with 252 additions and 17 deletions
@ -0,0 +1,97 @@
@@ -0,0 +1,97 @@
|
||||
/* |
||||
* Copyright 2002-2024 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.test.context.bean.override.mockito; |
||||
|
||||
import java.util.List; |
||||
|
||||
import org.junit.jupiter.api.Test; |
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired; |
||||
import org.springframework.context.annotation.Bean; |
||||
import org.springframework.context.annotation.Configuration; |
||||
import org.springframework.test.context.bean.override.example.ExampleService; |
||||
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
import static org.mockito.BDDMockito.given; |
||||
import static org.springframework.test.context.bean.override.mockito.MockReset.AFTER; |
||||
import static org.springframework.test.context.bean.override.mockito.MockReset.BEFORE; |
||||
import static org.springframework.test.mockito.MockitoAssertions.assertIsMock; |
||||
|
||||
/** |
||||
* Integration tests for {@link MockitoBean @MockitoBean} where duplicate mocks |
||||
* are created to replace the same existing bean, selected by-type. |
||||
* |
||||
* <p>In other words, this test class demonstrates how one {@code @MockitoBean} |
||||
* can silently override another {@code @MockitoBean}. |
||||
* |
||||
* @author Sam Brannen |
||||
* @since 6.2.1 |
||||
* @see <a href="https://github.com/spring-projects/spring-framework/issues/34056">gh-34056</a> |
||||
* @see MockitoBeanDuplicateTypeCreationIntegrationTests |
||||
* @see MockitoSpyBeanDuplicateTypeIntegrationTests |
||||
*/ |
||||
@SpringJUnitConfig |
||||
public class MockitoBeanDuplicateTypeReplacementIntegrationTests { |
||||
|
||||
@MockitoBean(reset = AFTER) |
||||
ExampleService mock1; |
||||
|
||||
@MockitoBean(reset = BEFORE) |
||||
ExampleService mock2; |
||||
|
||||
@Autowired |
||||
List<ExampleService> services; |
||||
|
||||
/** |
||||
* One could argue that we would ideally expect an exception to be thrown when |
||||
* two competing mocks are created to replace the same existing bean; however, |
||||
* we currently only log a warning in such cases. |
||||
* <p>This method therefore asserts the status quo in terms of behavior. |
||||
* <p>And the log can be manually checked to verify that an appropriate |
||||
* warning was logged. |
||||
*/ |
||||
@Test |
||||
void onlyOneMockShouldHaveBeenCreated() { |
||||
// Currently logs something similar to the following.
|
||||
//
|
||||
// WARN - Bean with name 'exampleService' was overridden by multiple handlers:
|
||||
// [MockitoBeanOverrideHandler@5478ce1e ..., MockitoBeanOverrideHandler@5edc70ed ...]
|
||||
|
||||
// Last one wins: there's only one physical mock
|
||||
assertThat(services).containsExactly(mock2); |
||||
assertThat(mock1).isSameAs(mock2); |
||||
|
||||
assertIsMock(mock2); |
||||
assertThat(MockReset.get(mock2)).as("MockReset").isEqualTo(BEFORE); |
||||
|
||||
assertThat(mock2.greeting()).isNull(); |
||||
given(mock2.greeting()).willReturn("mocked"); |
||||
assertThat(mock2.greeting()).isEqualTo("mocked"); |
||||
} |
||||
|
||||
|
||||
@Configuration |
||||
static class Config { |
||||
|
||||
@Bean |
||||
ExampleService exampleService() { |
||||
return () -> "@Bean"; |
||||
} |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,103 @@
@@ -0,0 +1,103 @@
|
||||
/* |
||||
* Copyright 2002-2024 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.test.context.bean.override.mockito; |
||||
|
||||
import java.util.List; |
||||
|
||||
import org.junit.jupiter.api.Test; |
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired; |
||||
import org.springframework.context.annotation.Bean; |
||||
import org.springframework.context.annotation.Configuration; |
||||
import org.springframework.test.context.bean.override.convention.TestBean; |
||||
import org.springframework.test.context.bean.override.example.ExampleService; |
||||
import org.springframework.test.context.bean.override.example.RealExampleService; |
||||
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
import static org.mockito.BDDMockito.given; |
||||
import static org.springframework.test.mockito.MockitoAssertions.assertIsMock; |
||||
|
||||
/** |
||||
* Integration tests for Bean Overrides where a {@link MockitoBean @MockitoBean} |
||||
* overrides a {@link TestBean @TestBean} when trying to replace the same existing |
||||
* bean, selected by-type. |
||||
* |
||||
* <p>In other words, this test class demonstrates how one Bean Override can |
||||
* silently override another Bean Override. |
||||
* |
||||
* @author Sam Brannen |
||||
* @since 6.2.1 |
||||
* @see <a href="https://github.com/spring-projects/spring-framework/issues/34056">gh-34056</a> |
||||
* @see MockitoBeanDuplicateTypeCreationIntegrationTests |
||||
* @see MockitoBeanDuplicateTypeReplacementIntegrationTests |
||||
*/ |
||||
@SpringJUnitConfig |
||||
public class MockitoBeanOverridesTestBeanIntegrationTests { |
||||
|
||||
@TestBean |
||||
ExampleService testService; |
||||
|
||||
@MockitoBean |
||||
ExampleService mockService; |
||||
|
||||
@Autowired |
||||
List<ExampleService> services; |
||||
|
||||
|
||||
static ExampleService testService() { |
||||
return new RealExampleService("@TestBean"); |
||||
} |
||||
|
||||
|
||||
/** |
||||
* One could argue that we would ideally expect an exception to be thrown when |
||||
* two competing overrides are created to replace the same existing bean; however, |
||||
* we currently only log a warning in such cases. |
||||
* <p>This method therefore asserts the status quo in terms of behavior. |
||||
* <p>And the log can be manually checked to verify that an appropriate |
||||
* warning was logged. |
||||
*/ |
||||
@Test |
||||
void mockitoBeanShouldOverrideTestBean() { |
||||
// Currently logs something similar to the following.
|
||||
//
|
||||
// WARN - Bean with name 'exampleService' was overridden by multiple handlers:
|
||||
// [TestBeanOverrideHandler@770beef5 ..., MockitoBeanOverrideHandler@6dd1f638 ...]
|
||||
|
||||
// Last override wins...
|
||||
assertThat(services).containsExactly(mockService); |
||||
assertThat(testService).isSameAs(mockService); |
||||
|
||||
assertIsMock(mockService); |
||||
|
||||
assertThat(mockService.greeting()).isNull(); |
||||
given(mockService.greeting()).willReturn("mocked"); |
||||
assertThat(mockService.greeting()).isEqualTo("mocked"); |
||||
} |
||||
|
||||
|
||||
@Configuration |
||||
static class Config { |
||||
|
||||
@Bean |
||||
ExampleService exampleService() { |
||||
return () -> "@Bean"; |
||||
} |
||||
} |
||||
|
||||
} |
||||
Loading…
Reference in new issue