diff --git a/spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverrideContextCustomizerFactory.java b/spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverrideContextCustomizerFactory.java index 29cc22fc225..544b43e996b 100644 --- a/spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverrideContextCustomizerFactory.java +++ b/spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverrideContextCustomizerFactory.java @@ -24,6 +24,7 @@ import org.springframework.lang.Nullable; import org.springframework.test.context.ContextConfigurationAttributes; import org.springframework.test.context.ContextCustomizerFactory; import org.springframework.test.context.TestContextAnnotationUtils; +import org.springframework.util.Assert; /** * {@link ContextCustomizerFactory} implementation that provides support for @@ -51,10 +52,13 @@ class BeanOverrideContextCustomizerFactory implements ContextCustomizerFactory { } private void findBeanOverrideHandler(Class testClass, Set handlers) { - handlers.addAll(BeanOverrideHandler.forTestClass(testClass)); if (TestContextAnnotationUtils.searchEnclosingClass(testClass)) { findBeanOverrideHandler(testClass.getEnclosingClass(), handlers); } + BeanOverrideHandler.forTestClass(testClass).forEach(handler -> + Assert.state(handlers.add(handler), () -> + "Duplicate BeanOverrideHandler discovered in test class %s: %s" + .formatted(testClass.getName(), handler))); } } diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/BeanOverrideContextCustomizerFactoryTests.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/BeanOverrideContextCustomizerFactoryTests.java index 0dbef0ee7df..2ed2498993e 100644 --- a/spring-test/src/test/java/org/springframework/test/context/bean/override/BeanOverrideContextCustomizerFactoryTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/bean/override/BeanOverrideContextCustomizerFactoryTests.java @@ -19,18 +19,20 @@ package org.springframework.test.context.bean.override; import java.util.Collections; import java.util.function.Consumer; -import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.springframework.lang.Nullable; import org.springframework.test.context.bean.override.DummyBean.DummyBeanOverrideProcessor.DummyBeanOverrideHandler; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalStateException; /** * Tests for {@link BeanOverrideContextCustomizerFactory}. * * @author Stephane Nicoll + * @author Sam Brannen + * @since 6.2 */ class BeanOverrideContextCustomizerFactoryTests { @@ -65,6 +67,15 @@ class BeanOverrideContextCustomizerFactoryTests { .hasSize(2); } + @Test // gh-34054 + void failsWithDuplicateBeanOverrides() { + Class testClass = DuplicateOverridesTestCase.class; + assertThatIllegalStateException() + .isThrownBy(() -> createContextCustomizer(testClass)) + .withMessageStartingWith("Duplicate BeanOverrideHandler discovered in test class " + testClass.getName()) + .withMessageContaining("DummyBeanOverrideHandler"); + } + private Consumer dummyHandler(@Nullable String beanName, Class beanType) { return dummyHandler(beanName, beanType, BeanOverrideStrategy.REPLACE); @@ -80,15 +91,15 @@ class BeanOverrideContextCustomizerFactoryTests { } @Nullable - BeanOverrideContextCustomizer createContextCustomizer(Class testClass) { + private BeanOverrideContextCustomizer createContextCustomizer(Class testClass) { return this.factory.createContextCustomizer(testClass, Collections.emptyList()); } + static class Test1 { @DummyBean private String descriptor; - } static class Test2 { @@ -96,17 +107,25 @@ class BeanOverrideContextCustomizerFactoryTests { @DummyBean private String name; - @Nested + // @Nested class Orange { } - @Nested + // @Nested class Green { @DummyBean(beanName = "counterBean") private Integer counter; - } } + static class DuplicateOverridesTestCase { + + @DummyBean(beanName = "text") + String text1; + + @DummyBean(beanName = "text") + String text2; + } + } diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/convention/TestBeanForByNameLookupIntegrationTests.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/convention/TestBeanForByNameLookupIntegrationTests.java index 8274cd5ccfc..63dfd4e9604 100644 --- a/spring-test/src/test/java/org/springframework/test/context/bean/override/convention/TestBeanForByNameLookupIntegrationTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/bean/override/convention/TestBeanForByNameLookupIntegrationTests.java @@ -40,21 +40,9 @@ public class TestBeanForByNameLookupIntegrationTests { @TestBean(name = "field") String field; - @TestBean(name = "nestedField") - String nestedField; - - @TestBean(name = "field") - String renamed1; - - @TestBean(name = "nestedField") - String renamed2; - @TestBean(name = "methodRenamed1", methodName = "field") String methodRenamed1; - @TestBean(name = "methodRenamed2", methodName = "nestedField") - String methodRenamed2; - static String field() { return "fieldOverride"; } @@ -66,62 +54,75 @@ public class TestBeanForByNameLookupIntegrationTests { @Test void fieldHasOverride(ApplicationContext ctx) { assertThat(ctx.getBean("field")).as("applicationContext").isEqualTo("fieldOverride"); - assertThat(this.field).as("injection point").isEqualTo("fieldOverride"); - } - - @Test - void renamedFieldHasOverride(ApplicationContext ctx) { - assertThat(ctx.getBean("field")).as("applicationContext").isEqualTo("fieldOverride"); - assertThat(this.renamed1).as("injection point").isEqualTo("fieldOverride"); + assertThat(field).as("injection point").isEqualTo("fieldOverride"); } @Test void fieldWithMethodNameHasOverride(ApplicationContext ctx) { assertThat(ctx.getBean("methodRenamed1")).as("applicationContext").isEqualTo("fieldOverride"); - assertThat(this.methodRenamed1).as("injection point").isEqualTo("fieldOverride"); + assertThat(methodRenamed1).as("injection point").isEqualTo("fieldOverride"); } @Nested - @DisplayName("With @TestBean in enclosing class") + @DisplayName("With @TestBean in enclosing class and in @Nested class") public class TestBeanFieldInEnclosingClassTests { + @TestBean(name = "nestedField") + String nestedField; + + @TestBean(name = "methodRenamed2", methodName = "nestedField") + String methodRenamed2; + + @Test void fieldHasOverride(ApplicationContext ctx) { - assertThat(ctx.getBean("nestedField")).as("applicationContext").isEqualTo("nestedFieldOverride"); - assertThat(nestedField).isEqualTo("nestedFieldOverride"); + assertThat(ctx.getBean("field")).as("applicationContext").isEqualTo("fieldOverride"); + assertThat(field).as("injection point").isEqualTo("fieldOverride"); + } + + @Test + void fieldWithMethodNameHasOverride(ApplicationContext ctx) { + assertThat(ctx.getBean("methodRenamed1")).as("applicationContext").isEqualTo("fieldOverride"); + assertThat(methodRenamed1).as("injection point").isEqualTo("fieldOverride"); } @Test - void renamedFieldHasOverride(ApplicationContext ctx) { + void nestedFieldHasOverride(ApplicationContext ctx) { assertThat(ctx.getBean("nestedField")).as("applicationContext").isEqualTo("nestedFieldOverride"); - assertThat(renamed2).isEqualTo("nestedFieldOverride"); + assertThat(nestedField).isEqualTo("nestedFieldOverride"); } @Test - void fieldWithMethodNameHasOverride(ApplicationContext ctx) { + void nestedFieldWithMethodNameHasOverride(ApplicationContext ctx) { assertThat(ctx.getBean("methodRenamed2")).as("applicationContext").isEqualTo("nestedFieldOverride"); assertThat(methodRenamed2).isEqualTo("nestedFieldOverride"); } @Nested - @DisplayName("With @TestBean in the enclosing class of the enclosing class") + @DisplayName("With @TestBean in the enclosing classes") public class TestBeanFieldInEnclosingClassLevel2Tests { @Test void fieldHasOverride(ApplicationContext ctx) { - assertThat(ctx.getBean("nestedField")).as("applicationContext").isEqualTo("nestedFieldOverride"); - assertThat(nestedField).isEqualTo("nestedFieldOverride"); + assertThat(ctx.getBean("field")).as("applicationContext").isEqualTo("fieldOverride"); + assertThat(field).as("injection point").isEqualTo("fieldOverride"); + } + + @Test + void fieldWithMethodNameHasOverride(ApplicationContext ctx) { + assertThat(ctx.getBean("methodRenamed1")).as("applicationContext").isEqualTo("fieldOverride"); + assertThat(methodRenamed1).as("injection point").isEqualTo("fieldOverride"); } @Test - void renamedFieldHasOverride(ApplicationContext ctx) { + void nestedFieldHasOverride(ApplicationContext ctx) { assertThat(ctx.getBean("nestedField")).as("applicationContext").isEqualTo("nestedFieldOverride"); - assertThat(renamed2).isEqualTo("nestedFieldOverride"); + assertThat(nestedField).isEqualTo("nestedFieldOverride"); } @Test - void fieldWithMethodNameHasOverride(ApplicationContext ctx) { + void nestedFieldWithMethodNameHasOverride(ApplicationContext ctx) { assertThat(ctx.getBean("methodRenamed2")).as("applicationContext").isEqualTo("nestedFieldOverride"); assertThat(methodRenamed2).isEqualTo("nestedFieldOverride"); } @@ -133,25 +134,25 @@ public class TestBeanForByNameLookupIntegrationTests { public class TestBeanFactoryMethodInEnclosingClassTests { @TestBean(methodName = "nestedField", name = "nestedField") - String nestedField2; + String nestedField; @Test - void fieldHasOverride(ApplicationContext ctx) { + void nestedFieldHasOverride(ApplicationContext ctx) { assertThat(ctx.getBean("nestedField")).as("applicationContext").isEqualTo("nestedFieldOverride"); - assertThat(this.nestedField2).isEqualTo("nestedFieldOverride"); + assertThat(nestedField).isEqualTo("nestedFieldOverride"); } @Nested @DisplayName("With factory method in the enclosing class of the enclosing class") public class TestBeanFactoryMethodInEnclosingClassLevel2Tests { - @TestBean(methodName = "nestedField", name = "nestedField") - String nestedField2; + @TestBean(methodName = "nestedField", name = "nestedNestedField") + String nestedNestedField; @Test - void fieldHasOverride(ApplicationContext ctx) { - assertThat(ctx.getBean("nestedField")).as("applicationContext").isEqualTo("nestedFieldOverride"); - assertThat(this.nestedField2).isEqualTo("nestedFieldOverride"); + void nestedFieldHasOverride(ApplicationContext ctx) { + assertThat(ctx.getBean("nestedNestedField")).as("applicationContext").isEqualTo("nestedFieldOverride"); + assertThat(nestedNestedField).isEqualTo("nestedFieldOverride"); } } } diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoBeanForByNameLookupIntegrationTests.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoBeanForByNameLookupIntegrationTests.java index d7ddafaa43c..46643178eb3 100644 --- a/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoBeanForByNameLookupIntegrationTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoBeanForByNameLookupIntegrationTests.java @@ -20,6 +20,8 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -32,6 +34,10 @@ import static org.assertj.core.api.Assertions.assertThat; /** * Integration tests for {@link MockitoBean} that use by-name lookup. + * + * @author Simon Baslé + * @author Sam Brannen + * @since 6.2 */ @SpringJUnitConfig public class MockitoBeanForByNameLookupIntegrationTests { @@ -39,20 +45,8 @@ public class MockitoBeanForByNameLookupIntegrationTests { @MockitoBean("field") ExampleService field; - @MockitoBean("nestedField") - ExampleService nestedField; - - @MockitoBean("field") - ExampleService renamed1; - - @MockitoBean("nestedField") - ExampleService renamed2; - @MockitoBean("nonExistingBean") - ExampleService nonExisting1; - - @MockitoBean("nestedNonExistingBean") - ExampleService nonExisting2; + ExampleService nonExisting; @Test @@ -60,11 +54,9 @@ public class MockitoBeanForByNameLookupIntegrationTests { assertThat(ctx.getBean("field")) .isInstanceOf(ExampleService.class) .satisfies(MockitoAssertions::assertIsMock) - .isSameAs(this.field) - .isSameAs(this.renamed1); + .isSameAs(field); - assertThat(this.field.greeting()).as("mocked greeting").isNull(); - assertThat(this.renamed1.greeting()).as("mocked greeting").isNull(); + assertThat(field.greeting()).as("mocked greeting").isNull(); } @Test @@ -72,31 +64,65 @@ public class MockitoBeanForByNameLookupIntegrationTests { assertThat(ctx.getBean("nonExistingBean")) .isInstanceOf(ExampleService.class) .satisfies(MockitoAssertions::assertIsMock) - .isSameAs(this.nonExisting1); + .isSameAs(nonExisting); - assertThat(this.nonExisting1.greeting()).as("mocked greeting").isNull(); + assertThat(nonExisting.greeting()).as("mocked greeting").isNull(); } @Nested - @DisplayName("With @MockitoBean in enclosing class") + @DisplayName("With @MockitoBean in enclosing class and in @Nested class") public class MockitoBeanNestedTests { + @Autowired + @Qualifier("field") + ExampleService localField; + + @Autowired + @Qualifier("nonExistingBean") + ExampleService localNonExisting; + + @MockitoBean("nestedField") + ExampleService nestedField; + + @MockitoBean("nestedNonExistingBean") + ExampleService nestedNonExisting; + + @Test void fieldAndRenamedFieldHaveSameOverride(ApplicationContext ctx) { - assertThat(ctx.getBean("nestedField")) + assertThat(ctx.getBean("field")) .isInstanceOf(ExampleService.class) .satisfies(MockitoAssertions::assertIsMock) - .isSameAs(nestedField) - .isSameAs(renamed2); + .isSameAs(localField); + + assertThat(localField.greeting()).as("mocked greeting").isNull(); } @Test void fieldIsMockedWhenNoOriginalBean(ApplicationContext ctx) { + assertThat(ctx.getBean("nonExistingBean")) + .isInstanceOf(ExampleService.class) + .satisfies(MockitoAssertions::assertIsMock) + .isSameAs(localNonExisting); + + assertThat(localNonExisting.greeting()).as("mocked greeting").isNull(); + } + + @Test + void nestedFieldAndRenamedFieldHaveSameOverride(ApplicationContext ctx) { + assertThat(ctx.getBean("nestedField")) + .isInstanceOf(ExampleService.class) + .satisfies(MockitoAssertions::assertIsMock) + .isSameAs(nestedField); + } + + @Test + void nestedFieldIsMockedWhenNoOriginalBean(ApplicationContext ctx) { assertThat(ctx.getBean("nestedNonExistingBean")) .isInstanceOf(ExampleService.class) .satisfies(MockitoAssertions::assertIsMock) - .isSameAs(nonExisting2); + .isSameAs(nestedNonExisting); } } diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoSpyBeanDuplicateTypeAndNameIntegrationTests.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoSpyBeanDuplicateTypeAndNameIntegrationTests.java deleted file mode 100644 index 66bc7030dcb..00000000000 --- a/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoSpyBeanDuplicateTypeAndNameIntegrationTests.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * 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.bean.override.example.RealExampleService; -import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.test.mockito.MockitoAssertions.assertIsNotSpy; -import static org.springframework.test.mockito.MockitoAssertions.assertIsSpy; - -/** - * Integration tests for duplicate {@link MockitoSpyBean @MockitoSpyBean} - * declarations for the same target bean, selected by-name. - * - * @author Sam Brannen - * @since 6.2.1 - * @see MockitoBeanDuplicateTypeIntegrationTests - * @see MockitoSpyBeanDuplicateTypeIntegrationTests - */ -@SpringJUnitConfig -public class MockitoSpyBeanDuplicateTypeAndNameIntegrationTests { - - @MockitoSpyBean("exampleService1") - ExampleService service1; - - @MockitoSpyBean("exampleService1") - ExampleService service2; - - @Autowired - ExampleService exampleService2; - - @Autowired - List services; - - - @Test - void duplicateMocksShouldHaveBeenCreated() { - assertThat(service1).isSameAs(service2); - assertThat(services).containsExactly(service1, exampleService2); - - assertIsSpy(service1, "service1"); - assertIsNotSpy(exampleService2, "exampleService2"); - } - - - @Configuration(proxyBeanMethods = false) - static class Config { - - @Bean - ExampleService exampleService1() { - return new RealExampleService("@Bean 1"); - } - - @Bean - ExampleService exampleService2() { - return new RealExampleService("@Bean 2"); - } - } - -} diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoSpyBeanForByNameLookupIntegrationTests.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoSpyBeanForByNameLookupIntegrationTests.java index 17989e22d14..117570a0ce8 100644 --- a/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoSpyBeanForByNameLookupIntegrationTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoSpyBeanForByNameLookupIntegrationTests.java @@ -20,6 +20,8 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -44,9 +46,6 @@ public class MockitoSpyBeanForByNameLookupIntegrationTests { @MockitoSpyBean("field1") ExampleService field; - @MockitoSpyBean("field1") - ExampleService renamed1; - @Test void fieldHasOverride(ApplicationContext ctx) { @@ -58,44 +57,36 @@ public class MockitoSpyBeanForByNameLookupIntegrationTests { assertThat(field.greeting()).isEqualTo("bean1"); } - @Test - void renamedFieldHasOverride(ApplicationContext ctx) { - assertThat(ctx.getBean("field1")) - .isInstanceOf(ExampleService.class) - .satisfies(MockitoAssertions::assertIsSpy) - .isSameAs(renamed1); - - assertThat(renamed1.greeting()).isEqualTo("bean1"); - } @Nested @DisplayName("With @MockitoSpyBean in enclosing class and in @Nested class") public class MockitoSpyBeanNestedTests { - @MockitoSpyBean("field2") - ExampleService nestedField; + @Autowired + @Qualifier("field1") + ExampleService localField; @MockitoSpyBean("field2") - ExampleService renamed2; + ExampleService nestedField; @Test void fieldHasOverride(ApplicationContext ctx) { - assertThat(ctx.getBean("field2")) + assertThat(ctx.getBean("field1")) .isInstanceOf(ExampleService.class) .satisfies(MockitoAssertions::assertIsSpy) - .isSameAs(nestedField); + .isSameAs(localField); - assertThat(nestedField.greeting()).isEqualTo("bean2"); + assertThat(localField.greeting()).isEqualTo("bean1"); } @Test - void renamedFieldHasOverride(ApplicationContext ctx) { + void nestedFieldHasOverride(ApplicationContext ctx) { assertThat(ctx.getBean("field2")) .isInstanceOf(ExampleService.class) .satisfies(MockitoAssertions::assertIsSpy) - .isSameAs(renamed2); + .isSameAs(nestedField); - assertThat(renamed2.greeting()).isEqualTo("bean2"); + assertThat(nestedField.greeting()).isEqualTo("bean2"); } }