Use by-type semantics in bean overriding if no explicit name is provided

This change switches default behavior of `@TestBean`, `@MockitoBean` and
`@MockitoSpyBean` to match the bean definition / bean to override by
type in the case there is no explicit bean name provided via the
annotation. The previous behavior of using the annotated field's name
is still an option for implementors, but no longer the default.

Closes gh-32761
This commit is contained in:
Simon Baslé
2024-05-14 12:35:22 +02:00
parent fc07946e60
commit a86612a254
19 changed files with 483 additions and 73 deletions

View File

@@ -38,7 +38,7 @@ class OverrideMetadataTests {
@Test
void implicitConfigurations() throws Exception {
OverrideMetadata metadata = exampleOverride();
assertThat(metadata.getBeanName()).as("expectedBeanName").isEqualTo(metadata.getField().getName());
assertThat(metadata.getBeanName()).as("expectedBeanName").isNull();
}

View File

@@ -23,10 +23,10 @@ import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
@SpringJUnitConfig
class AbstractTestBeanIntegrationTestCase {
@TestBean
@TestBean(name = "someBean")
Pojo someBean;
@TestBean
@TestBean(name = "otherBean")
Pojo otherBean;
@TestBean(name = "thirdBean")

View File

@@ -0,0 +1,140 @@
/*
* 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.convention;
import org.junit.jupiter.api.Test;
import org.junit.platform.engine.TestExecutionResult;
import org.junit.platform.testkit.engine.EngineExecutionResults;
import org.junit.platform.testkit.engine.EngineTestKit;
import org.springframework.context.ApplicationContext;
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.bean.override.example.RealExampleService;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.fail;
import static org.assertj.core.api.InstanceOfAssertFactories.THROWABLE;
import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass;
@SpringJUnitConfig(TestBeanByTypeIntegrationTests.ConfigByType.class)
public class TestBeanByTypeIntegrationTests {
@TestBean
ExampleService anyNameForService;
static ExampleService anyNameForServiceTestOverride() {
return new RealExampleService("Mocked greeting");
}
@Test
void overrideIsFoundByType(ApplicationContext ctx) {
assertThat(this.anyNameForService)
.isSameAs(ctx.getBean("example"))
.isSameAs(ctx.getBean(ExampleService.class));
assertThat(this.anyNameForService.greeting()).isEqualTo("Mocked greeting");
}
@Test
void zeroCandidates() {
Class<?> caseClass = CaseNone.class;
EngineExecutionResults results = EngineTestKit.engine("junit-jupiter")//
.selectors(selectClass(caseClass))//
.execute();
assertThat(results.allEvents().failed().stream()).hasSize(1).first()
.satisfies(e -> assertThat(e.getRequiredPayload(TestExecutionResult.class)
.getThrowable()).get(THROWABLE)
.cause()
.isInstanceOf(IllegalStateException.class)
.hasMessage("Unable to select a bean definition to override, 0 bean definitions " +
"found of type %s (as required by annotated field '%s.example')",
ExampleService.class.getName(), caseClass.getSimpleName()));
}
@Test
void tooManyCandidates() {
Class<?> caseClass = CaseTooMany.class;
EngineExecutionResults results = EngineTestKit.engine("junit-jupiter")//
.selectors(selectClass(caseClass))//
.execute();
assertThat(results.allEvents().failed().stream()).hasSize(1).first()
.satisfies(e -> assertThat(e.getRequiredPayload(TestExecutionResult.class)
.getThrowable()).get(THROWABLE)
.cause()
.isInstanceOf(IllegalStateException.class)
.hasMessage("Unable to select a bean definition to override, 2 bean definitions " +
"found of type %s (as required by annotated field '%s.example')",
ExampleService.class.getName(), caseClass.getSimpleName()));
}
@Configuration
static class ConfigByType {
@Bean("example")
ExampleService bean1() {
return new RealExampleService("Production hello");
}
}
@SpringJUnitConfig(FailingNone.class)
static class CaseNone {
@TestBean
ExampleService example;
@Test
void test() {}
static ExampleService exampleTestOverride() {
fail("unexpected override");
return null;
}
}
@Configuration
static class FailingNone {
}
@SpringJUnitConfig(FailingTooMany.class)
static class CaseTooMany {
@TestBean
ExampleService example;
@Test
void test() {}
static ExampleService exampleTestOverride() {
fail("unexpected override");
return null;
}
}
@Configuration
static class FailingTooMany {
@Bean
ExampleService bean1() {
return new RealExampleService("1 Hello");
}
@Bean
ExampleService bean2() {
return new RealExampleService("2 Hello");
}
}
}

View File

@@ -39,10 +39,10 @@ public class TestBeanInheritanceIntegrationTests {
@DisplayName("Concrete inherited test with correct @TestBean setup")
class ConcreteTestBeanIntegrationTests extends AbstractTestBeanIntegrationTestCase {
@TestBean(methodName = "commonBeanOverride")
@TestBean(name = "pojo", methodName = "commonBeanOverride")
Pojo pojo;
@TestBean(methodName = "nestedBeanOverride")
@TestBean(name = "pojo2", methodName = "nestedBeanOverride")
Pojo pojo2;
static Pojo someBeanTestOverride() {

View File

@@ -36,10 +36,10 @@ import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass
@SpringJUnitConfig
public class TestBeanIntegrationTests {
@TestBean
@TestBean(name = "field")
String field;
@TestBean
@TestBean(name = "nestedField")
String nestedField;
@TestBean(name = "field")
@@ -48,10 +48,10 @@ public class TestBeanIntegrationTests {
@TestBean(name = "nestedField")
String renamed2;
@TestBean(methodName = "fieldTestOverride")
@TestBean(name = "methodRenamed1", methodName = "fieldTestOverride")
String methodRenamed1;
@TestBean(methodName = "nestedFieldTestOverride")
@TestBean(name = "methodRenamed2", methodName = "nestedFieldTestOverride")
String methodRenamed2;
static String fieldTestOverride() {
@@ -69,7 +69,7 @@ public class TestBeanIntegrationTests {
}
@Test
void fieldWithBeanNameHasOverride(ApplicationContext ctx) {
void renamedFieldHasOverride(ApplicationContext ctx) {
assertThat(ctx.getBean("field")).as("applicationContext").isEqualTo("fieldOverride");
assertThat(this.renamed1).as("injection point").isEqualTo("fieldOverride");
}
@@ -153,7 +153,7 @@ public class TestBeanIntegrationTests {
}
@Test
void fieldWithBeanNameHasOverride(ApplicationContext ctx) {
void renamedFieldHasOverride(ApplicationContext ctx) {
assertThat(ctx.getBean("nestedField")).as("applicationContext").isEqualTo("nestedFieldOverride");
assertThat(TestBeanIntegrationTests.this.renamed2).isEqualTo("nestedFieldOverride");
}
@@ -209,7 +209,7 @@ public class TestBeanIntegrationTests {
@SpringJUnitConfig
static class Failing1 {
@TestBean
@TestBean(name = "noOriginalBean")
String noOriginalBean;
@Test

View File

@@ -99,7 +99,7 @@ class TestOverrideMetadata extends OverrideMetadata {
if (StringUtils.hasText(this.beanName)) {
return this.beanName;
}
return super.getBeanName();
return getField().getName();
}
String getAnnotationMethodName() {

View File

@@ -33,10 +33,10 @@ import static org.assertj.core.api.Assertions.assertThat;
@SpringJUnitConfig
public class MockitoBeanIntegrationTests {
@MockitoBean
@MockitoBean(name = "field")
ExampleService field;
@MockitoBean
@MockitoBean(name = "nestedField")
ExampleService nestedField;
@MockitoBean(name = "field")
@@ -63,7 +63,7 @@ public class MockitoBeanIntegrationTests {
}
@Test
void fieldWithBeanNameHasOverride(ApplicationContext ctx) {
void renamedFieldHasOverride(ApplicationContext ctx) {
assertThat(ctx.getBean("field"))
.isInstanceOf(ExampleService.class)
.satisfies(o -> assertThat(Mockito.mockingDetails(o).isMock())
@@ -98,7 +98,7 @@ public class MockitoBeanIntegrationTests {
}
@Test
void fieldWithBeanNameHasOverride(ApplicationContext ctx) {
void renamedFieldHasOverride(ApplicationContext ctx) {
assertThat(ctx.getBean("nestedField"))
.isInstanceOf(ExampleService.class)
.satisfies(o -> assertThat(Mockito.mockingDetails(o).isMock())

View File

@@ -0,0 +1,223 @@
/*
* 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 org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.platform.engine.TestExecutionResult;
import org.junit.platform.testkit.engine.EngineExecutionResults;
import org.junit.platform.testkit.engine.EngineTestKit;
import org.mockito.Mockito;
import org.springframework.context.ApplicationContext;
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.bean.override.example.RealExampleService;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.InstanceOfAssertFactories.THROWABLE;
import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass;
import static org.mockito.BDDMockito.when;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
public class MockitoByTypeIntegrationTests {
@Nested
@SpringJUnitConfig(ConfigByType.class)
class Mock {
@MockitoBean
ExampleService anyNameForService;
@Test
void overrideIsFoundByType(ApplicationContext ctx) {
assertThat(this.anyNameForService)
.satisfies(o -> assertThat(Mockito.mockingDetails(o).isMock())
.as("isMock").isTrue())
.isSameAs(ctx.getBean("example"))
.isSameAs(ctx.getBean(ExampleService.class));
when(this.anyNameForService.greeting()).thenReturn("Mocked greeting");
assertThat(this.anyNameForService.greeting()).isEqualTo("Mocked greeting");
verify(this.anyNameForService, times(1)).greeting();
verifyNoMoreInteractions(this.anyNameForService);
}
@Test
void zeroCandidates() {
Class<?> caseClass = CaseNone.class;
EngineExecutionResults results = EngineTestKit.engine("junit-jupiter")//
.selectors(selectClass(caseClass))//
.execute();
assertThat(results.allEvents().failed().stream()).hasSize(1).first()
.satisfies(e -> assertThat(e.getRequiredPayload(TestExecutionResult.class)
.getThrowable()).get(THROWABLE)
.cause()
.isInstanceOf(IllegalStateException.class)
.hasMessage("Unable to select a bean definition to override, 0 bean definitions " +
"found of type %s (as required by annotated field '%s.example')",
ExampleService.class.getName(), caseClass.getSimpleName()));
}
@Test
void tooManyCandidates() {
Class<?> caseClass = CaseTooMany.class;
EngineExecutionResults results = EngineTestKit.engine("junit-jupiter")//
.selectors(selectClass(caseClass))//
.execute();
assertThat(results.allEvents().failed().stream()).hasSize(1).first()
.satisfies(e -> assertThat(e.getRequiredPayload(TestExecutionResult.class)
.getThrowable()).get(THROWABLE)
.cause()
.isInstanceOf(IllegalStateException.class)
.hasMessage("Unable to select a bean definition to override, 2 bean definitions " +
"found of type %s (as required by annotated field '%s.example')",
ExampleService.class.getName(), caseClass.getSimpleName()));
}
@SpringJUnitConfig(FailingNone.class)
static class CaseNone {
@MockitoBean
ExampleService example;
@Test
void test() {
assertThat(example).isNotNull();
}
}
@SpringJUnitConfig(FailingTooMany.class)
static class CaseTooMany {
@MockitoBean
ExampleService example;
@Test
void test() {
assertThat(example).isNotNull();
}
}
}
@Nested
@SpringJUnitConfig(ConfigByType.class)
class Spy {
@MockitoSpyBean
ExampleService anyNameForService;
@Test
void overrideIsFoundByType(ApplicationContext ctx) {
assertThat(this.anyNameForService)
.satisfies(o -> assertThat(Mockito.mockingDetails(o).isSpy())
.as("isSpy").isTrue())
.isSameAs(ctx.getBean("example"))
.isSameAs(ctx.getBean(ExampleService.class));
assertThat(this.anyNameForService.greeting()).isEqualTo("Production hello");
verify(this.anyNameForService, times(1)).greeting();
verifyNoMoreInteractions(this.anyNameForService);
}
@Test
void zeroCandidates() {
Class<?> caseClass = CaseNone.class;
EngineExecutionResults results = EngineTestKit.engine("junit-jupiter")//
.selectors(selectClass(caseClass))//
.execute();
assertThat(results.allEvents().failed().stream()).hasSize(1).first()
.satisfies(e -> assertThat(e.getRequiredPayload(TestExecutionResult.class)
.getThrowable()).get(THROWABLE)
.cause()
.isInstanceOf(IllegalStateException.class)
.hasMessage("Unable to select a bean definition to override, 0 bean definitions " +
"found of type %s (as required by annotated field '%s.example')",
ExampleService.class.getName(), caseClass.getSimpleName()));
}
@Test
void tooManyCandidates() {
Class<?> caseClass = CaseTooMany.class;
EngineExecutionResults results = EngineTestKit.engine("junit-jupiter")//
.selectors(selectClass(caseClass))//
.execute();
assertThat(results.allEvents().failed().stream()).hasSize(1).first()
.satisfies(e -> assertThat(e.getRequiredPayload(TestExecutionResult.class)
.getThrowable()).get(THROWABLE)
.cause()
.isInstanceOf(IllegalStateException.class)
.hasMessage("Unable to select a bean definition to override, 2 bean definitions " +
"found of type %s (as required by annotated field '%s.example')",
ExampleService.class.getName(), caseClass.getSimpleName()));
}
@SpringJUnitConfig(FailingNone.class)
static class CaseNone {
@MockitoBean
ExampleService example;
@Test
void test() {
assertThat(example).isNotNull();
}
}
@SpringJUnitConfig(FailingTooMany.class)
static class CaseTooMany {
@MockitoBean
ExampleService example;
@Test
void test() {
assertThat(example).isNotNull();
}
}
}
@Configuration
static class ConfigByType {
@Bean("example")
ExampleService bean1() {
return new RealExampleService("Production hello");
}
}
@Configuration
static class FailingNone {
}
@Configuration
static class FailingTooMany {
@Bean
ExampleService bean1() {
return new RealExampleService("1 Hello");
}
@Bean
ExampleService bean2() {
return new RealExampleService("2 Hello");
}
}
}

View File

@@ -35,10 +35,10 @@ import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass
@SpringJUnitConfig(MockitoBeanIntegrationTests.Config.class)
public class MockitoSpyBeanIntegrationTests {
@MockitoSpyBean
@MockitoSpyBean(name = "field")
ExampleService field;
@MockitoSpyBean
@MockitoSpyBean(name = "nestedField")
ExampleService nestedField;
@MockitoSpyBean(name = "field")
@@ -57,11 +57,10 @@ public class MockitoSpyBeanIntegrationTests {
assertThat(this.field.greeting()).as("spied greeting")
.isEqualTo("Hello Field");
}
@Test
void fieldWithBeanNameHasOverride(ApplicationContext ctx) {
void renamedFieldHasOverride(ApplicationContext ctx) {
assertThat(ctx.getBean("field"))
.isInstanceOf(ExampleService.class)
.satisfies(o -> assertThat(Mockito.mockingDetails(o).isSpy())
@@ -73,7 +72,7 @@ public class MockitoSpyBeanIntegrationTests {
}
@Test
void failWhenBeanNotPresentFieldName() {
void failWhenBeanNotPresentByType() {
EngineExecutionResults results = EngineTestKit.engine("junit-jupiter")//
.selectors(selectClass(Failure1.class))//
.execute();
@@ -83,13 +82,14 @@ public class MockitoSpyBeanIntegrationTests {
.getThrowable()).get(THROWABLE)
.cause()
.isInstanceOf(IllegalStateException.class)
.hasMessage("Unable to override bean 'notPresent' by wrapping," +
" no existing bean instance by this name of type %s",
ExampleService.class.getName()));
.hasMessage("Unable to select a bean to override by wrapping, " +
"0 bean instances found of type %s " +
"(as required by annotated field '%s.notPresent')",
ExampleService.class.getName(), Failure1.class.getSimpleName()));
}
@Test
void failWhenBeanNotPresentExplicitName() {
void failWhenBeanNotPresentByExplicitName() {
EngineExecutionResults results = EngineTestKit.engine("junit-jupiter")//
.selectors(selectClass(Failure2.class))//
.execute();
@@ -99,8 +99,8 @@ public class MockitoSpyBeanIntegrationTests {
.getThrowable()).get(THROWABLE)
.cause()
.isInstanceOf(IllegalStateException.class)
.hasMessage("Unable to override bean 'notPresentAtAll' by wrapping," +
" no existing bean instance by this name of type %s",
.hasMessage("Unable to override bean 'notPresentAtAll' by wrapping; " +
"there is no existing bean instance with that name of type %s",
ExampleService.class.getName()));
}
@@ -122,7 +122,7 @@ public class MockitoSpyBeanIntegrationTests {
}
@Test
void fieldWithBeanNameHasOverride(ApplicationContext ctx) {
void renamedFieldHasOverride(ApplicationContext ctx) {
assertThat(ctx.getBean("nestedField"))
.isInstanceOf(ExampleService.class)
.satisfies(o -> assertThat(Mockito.mockingDetails(o).isSpy())