diff --git a/spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverrideBeanFactoryPostProcessor.java b/spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverrideBeanFactoryPostProcessor.java index 2ca613782c58..c741c1691b6f 100644 --- a/spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverrideBeanFactoryPostProcessor.java +++ b/spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverrideBeanFactoryPostProcessor.java @@ -133,7 +133,7 @@ private void registerReplaceDefinition(ConfigurableListableBeanFactory beanFacto Set candidates = getExistingBeanNamesByType(beanFactory, overrideMetadata, true); if (candidates.size() != 1) { Field f = overrideMetadata.getField(); - throw new IllegalStateException("Unable to select a bean definition to override, " + + throw new IllegalStateException("Unable to select a bean definition to override: " + candidates.size() + " bean definitions found of type " + overrideMetadata.getBeanType() + " (as required by annotated field '" + f.getDeclaringClass().getSimpleName() + "." + f.getName() + "')"); @@ -147,7 +147,7 @@ private void registerReplaceDefinition(ConfigurableListableBeanFactory beanFacto existingBeanDefinition = beanFactory.getBeanDefinition(beanName); } else if (enforceExistingDefinition) { - throw new IllegalStateException("Unable to override bean '" + beanName + "'; there is no" + + throw new IllegalStateException("Unable to override bean '" + beanName + "': there is no" + " bean definition to replace with that name of type " + overrideMetadata.getBeanType()); } } @@ -183,7 +183,7 @@ private void registerWrapBean(ConfigurableListableBeanFactory beanFactory, Overr Set candidateNames = getExistingBeanNamesByType(beanFactory, metadata, true); if (candidateNames.size() != 1) { Field f = metadata.getField(); - throw new IllegalStateException("Unable to select a bean to override by wrapping, " + + throw new IllegalStateException("Unable to select a bean to override by wrapping: " + candidateNames.size() + " bean instances found of type " + metadata.getBeanType() + " (as required by annotated field '" + f.getDeclaringClass().getSimpleName() + "." + f.getName() + "')"); @@ -193,7 +193,7 @@ private void registerWrapBean(ConfigurableListableBeanFactory beanFactory, Overr else { Set candidates = getExistingBeanNamesByType(beanFactory, metadata, false); if (!candidates.contains(beanName)) { - throw new IllegalStateException("Unable to override bean '" + beanName + "' by wrapping; there is no" + + throw new IllegalStateException("Unable to override bean '" + beanName + "' by wrapping: there is no" + " existing bean instance with that name of type " + metadata.getBeanType()); } } diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/BeanOverrideBeanFactoryPostProcessorTests.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/BeanOverrideBeanFactoryPostProcessorTests.java index 91bfd9a56687..2e53e436750a 100644 --- a/spring-test/src/test/java/org/springframework/test/context/bean/override/BeanOverrideBeanFactoryPostProcessorTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/bean/override/BeanOverrideBeanFactoryPostProcessorTests.java @@ -77,7 +77,7 @@ void cannotReplaceIfNoBeanMatching() { assertThatIllegalStateException() .isThrownBy(context::refresh) - .withMessage("Unable to override bean 'explicit'; there is no bean definition " + + .withMessage("Unable to override bean 'explicit': there is no bean definition " + "to replace with that name of type org.springframework.test.context.bean.override.example.ExampleService"); } diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/BeanOverrideTestSuite.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/BeanOverrideTestSuite.java new file mode 100644 index 000000000000..4d6049f6f9bd --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/bean/override/BeanOverrideTestSuite.java @@ -0,0 +1,56 @@ +/* + * 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; + +import org.junit.jupiter.api.ClassOrderer; +import org.junit.platform.suite.api.ConfigurationParameter; +import org.junit.platform.suite.api.ExcludeTags; +import org.junit.platform.suite.api.IncludeClassNamePatterns; +import org.junit.platform.suite.api.IncludeEngines; +import org.junit.platform.suite.api.SelectPackages; +import org.junit.platform.suite.api.Suite; + +/** + * JUnit Platform based test suite for tests that involve bean override support + * in the Spring TestContext Framework. + * + *

This suite is only intended to be used manually within an IDE. + * + *

Logging Configuration

+ * + *

In order for our log4j2 configuration to be used in an IDE, you must + * set the following system property before running any tests — for + * example, in Run Configurations in Eclipse. + * + *

+ * -Djava.util.logging.manager=org.apache.logging.log4j.jul.LogManager
+ * 
+ * + * @author Sam Brannen + * @since 6.2 + */ +@Suite +@IncludeEngines("junit-jupiter") +@SelectPackages("org.springframework.test.context.bean.override") +@IncludeClassNamePatterns(".*Tests$") +@ExcludeTags("failing-test-case") +@ConfigurationParameter( + key = ClassOrderer.DEFAULT_ORDER_PROPERTY_NAME, + value = "org.junit.jupiter.api.ClassOrderer$ClassName" +) +public class BeanOverrideTestSuite { +} diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/convention/AbstractTestBeanIntegrationTestCase.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/convention/AbstractTestBeanIntegrationTestCase.java index 3e6e4402c22a..c4fa549b9b0a 100644 --- a/spring-test/src/test/java/org/springframework/test/context/bean/override/convention/AbstractTestBeanIntegrationTestCase.java +++ b/spring-test/src/test/java/org/springframework/test/context/bean/override/convention/AbstractTestBeanIntegrationTestCase.java @@ -21,28 +21,28 @@ import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; @SpringJUnitConfig -class AbstractTestBeanIntegrationTestCase { +abstract class AbstractTestBeanIntegrationTestCase { - @TestBean(name = "someBean") - Pojo someBean; + @TestBean(name = "someBean") + Pojo someBean; - @TestBean(name = "otherBean") - Pojo otherBean; + @TestBean(name = "otherBean") + Pojo otherBean; - @TestBean(name = "thirdBean") - Pojo anotherBean; + @TestBean(name = "thirdBean") + Pojo anotherBean; - static Pojo otherBeanTestOverride() { - return new FakePojo("otherBean in superclass"); - } + static Pojo otherBeanTestOverride() { + return new FakePojo("otherBean in superclass"); + } - static Pojo thirdBeanTestOverride() { - return new FakePojo("third in superclass"); - } + static Pojo thirdBeanTestOverride() { + return new FakePojo("third in superclass"); + } - static Pojo commonBeanOverride() { - return new FakePojo("in superclass"); - } + static Pojo commonBeanOverride() { + return new FakePojo("in superclass"); + } interface Pojo { @@ -60,6 +60,7 @@ protected FakePojo(String value) { this.value = value; } + @Override public String getValue() { return this.value; } @@ -94,4 +95,5 @@ Pojo pojo2() { return new ProdPojo(); } } + } diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/convention/FailingTestBeanByTypeIntegrationTests.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/convention/FailingTestBeanByTypeIntegrationTests.java new file mode 100644 index 000000000000..3c440aaa42c1 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/bean/override/convention/FailingTestBeanByTypeIntegrationTests.java @@ -0,0 +1,120 @@ +/* + * 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.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.EngineTestKitUtils; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; + +import static org.assertj.core.api.Assertions.fail; +import static org.junit.platform.testkit.engine.EventConditions.finishedWithFailure; +import static org.junit.platform.testkit.engine.TestExecutionResultConditions.cause; +import static org.junit.platform.testkit.engine.TestExecutionResultConditions.instanceOf; +import static org.junit.platform.testkit.engine.TestExecutionResultConditions.message; + +/** + * {@link TestBean @TestBean} "by type" integration tests for failure scenarios. + * + * @author Simon Baslé + * @author Sam Brannen + * @since 6.2 + * @see TestBeanByTypeIntegrationTests + */ +class FailingTestBeanByTypeIntegrationTests { + + @Test + void zeroCandidates() { + Class testClass = NoMatchingBeansTestCase.class; + EngineTestKitUtils.executeTestsForClass(testClass).assertThatEvents().haveExactly(1, + finishedWithFailure( + cause( + instanceOf(IllegalStateException.class), + message(""" + Unable to select a bean definition to override: 0 bean definitions \ + found of type %s (as required by annotated field '%s.example')""" + .formatted(ExampleService.class.getName(), testClass.getSimpleName()))))); + } + + @Test + void tooManyCandidates() { + Class testClass = TooManyBeansTestCase.class; + EngineTestKitUtils.executeTestsForClass(testClass).assertThatEvents().haveExactly(1, + finishedWithFailure( + cause( + instanceOf(IllegalStateException.class), + message(""" + Unable to select a bean definition to override: 2 bean definitions \ + found of type %s (as required by annotated field '%s.example')""" + .formatted(ExampleService.class.getName(), testClass.getSimpleName()))))); + } + + + @SpringJUnitConfig + static class NoMatchingBeansTestCase { + + @TestBean + ExampleService example; + + @Test + void test() { + } + + static ExampleService exampleTestOverride() { + return fail("unexpected override"); + } + + @Configuration + static class Config { + } + } + + + @SpringJUnitConfig + static class TooManyBeansTestCase { + + @TestBean + ExampleService example; + + @Test + void test() { + } + + static ExampleService exampleTestOverride() { + return fail("unexpected override"); + } + + @Configuration + static class Config { + + @Bean + ExampleService bean1() { + return new RealExampleService("1 Hello"); + } + + @Bean + ExampleService bean2() { + return new RealExampleService("2 Hello"); + } + } + } + +} diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/convention/FailingTestBeanInheritanceIntegrationTests.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/convention/FailingTestBeanInheritanceIntegrationTests.java new file mode 100644 index 000000000000..701ed3a3726c --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/bean/override/convention/FailingTestBeanInheritanceIntegrationTests.java @@ -0,0 +1,88 @@ +/* + * 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.springframework.test.context.junit.EngineTestKitUtils; + +import static org.junit.platform.testkit.engine.EventConditions.finishedWithFailure; +import static org.junit.platform.testkit.engine.TestExecutionResultConditions.instanceOf; +import static org.junit.platform.testkit.engine.TestExecutionResultConditions.message; +import static org.springframework.test.context.junit.EngineTestKitUtils.rootCause; + +/** + * {@link TestBean @TestBean} inheritance integration tests for failure scenarios. + * + * @author Simon Baslé + * @author Sam Brannen + * @since 6.2 + * @see TestBeanInheritanceIntegrationTests + */ +class FailingTestBeanInheritanceIntegrationTests { + + @Test + void failsIfFieldInSupertypeButNoMethod() { + Class testClass = FieldInSupertypeButNoMethodTestCase.class; + EngineTestKitUtils.executeTestsForClass(testClass).assertThatEvents().haveExactly(1, + finishedWithFailure( + rootCause( + instanceOf(IllegalStateException.class), + message(""" + Failed to find a static test bean factory method in %s with return type %s \ + whose name matches one of the supported candidates [someBeanTestOverride]""" + .formatted(testClass.getName(), AbstractTestBeanIntegrationTestCase.Pojo.class.getName()))))); + } + + @Test + void failsIfMethod1InSupertypeAndMethod2InType() { + Class testClass = Method1InSupertypeAndMethod2InTypeTestCase.class; + EngineTestKitUtils.executeTestsForClass(testClass).assertThatEvents().haveExactly(1, + finishedWithFailure( + rootCause( + instanceOf(IllegalStateException.class), + message(""" + Found 2 competing static test bean factory methods in %s with return type %s \ + whose name matches one of the supported candidates \ + [thirdBeanTestOverride, anotherBeanTestOverride]""" + .formatted(testClass.getName(), AbstractTestBeanIntegrationTestCase.Pojo.class.getName()))))); + } + + + static class FieldInSupertypeButNoMethodTestCase extends AbstractTestBeanIntegrationTestCase { + + @Test + void test() { + } + } + + static class Method1InSupertypeAndMethod2InTypeTestCase extends AbstractTestBeanIntegrationTestCase { + + static Pojo someBeanTestOverride() { + return new FakePojo("ignored"); + } + + static Pojo anotherBeanTestOverride() { + return new FakePojo("sub2"); + } + + @Test + void test() { + } + } + +} diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/convention/FailingTestBeanIntegrationTests.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/convention/FailingTestBeanIntegrationTests.java index eb37a7082955..9c04f8a34a5a 100644 --- a/spring-test/src/test/java/org/springframework/test/context/bean/override/convention/FailingTestBeanIntegrationTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/bean/override/convention/FailingTestBeanIntegrationTests.java @@ -17,155 +17,116 @@ 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.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.test.context.aot.DisabledInAotMode; +import org.springframework.test.context.junit.EngineTestKitUtils; 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; +import static org.junit.platform.testkit.engine.EventConditions.finishedWithFailure; +import static org.junit.platform.testkit.engine.TestExecutionResultConditions.cause; +import static org.junit.platform.testkit.engine.TestExecutionResultConditions.instanceOf; +import static org.junit.platform.testkit.engine.TestExecutionResultConditions.message; +import static org.springframework.test.context.junit.EngineTestKitUtils.rootCause; /** * {@link TestBean @TestBean} integration tests for failure scenarios. * + * @author Simon Baslé + * @author Sam Brannen * @since 6.2 * @see TestBeanIntegrationTests */ -public class FailingTestBeanIntegrationTests { +class FailingTestBeanIntegrationTests { @Test void testBeanFailingNoFieldNameBean() { - EngineExecutionResults results = EngineTestKit.engine("junit-jupiter")// - .selectors(selectClass(NoOriginalBeanTestCase.class))// - .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 override bean 'noOriginalBean'; " + - "there is no bean definition to replace with that name of type java.lang.String")); + Class testClass = NoOriginalBeanTestCase.class; + EngineTestKitUtils.executeTestsForClass(testClass).assertThatEvents().haveExactly(1, + finishedWithFailure( + cause( + instanceOf(IllegalStateException.class), + message(""" + Unable to override bean 'noOriginalBean': there is no bean definition \ + to replace with that name of type java.lang.String""")))); } @Test void testBeanFailingNoExplicitNameBean() { - EngineExecutionResults results = EngineTestKit.engine("junit-jupiter")// - .selectors(selectClass(BeanDefinitionToOverrideNotPresentTestCase.class))// - .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 override bean 'notPresent'; " + - "there is no bean definition to replace with that name of type java.lang.String")); + Class testClass = BeanDefinitionToOverrideNotPresentTestCase.class; + EngineTestKitUtils.executeTestsForClass(testClass).assertThatEvents().haveExactly(1, + finishedWithFailure( + cause( + instanceOf(IllegalStateException.class), + message(""" + Unable to override bean 'notPresent': there is no bean definition \ + to replace with that name of type java.lang.String""")))); } @Test void testBeanFailingNoImplicitMethod() { Class testClass = ExplicitTestOverrideMethodNotPresentTestCase.class; - EngineExecutionResults results = EngineTestKit.engine("junit-jupiter")// - .selectors(selectClass(testClass))// - .execute(); - - assertThat(results.allEvents().failed().stream()).hasSize(1).first() - .satisfies(e -> assertThat(e.getRequiredPayload(TestExecutionResult.class) - .getThrowable()).get(THROWABLE) - .rootCause().isInstanceOf(IllegalStateException.class) - .hasMessage("Failed to find a static test bean factory method in %s " + - "with return type java.lang.String whose name matches one of the " + - "supported candidates [notPresent]", testClass.getName())); + EngineTestKitUtils.executeTestsForClass(testClass).assertThatEvents().haveExactly(1, + finishedWithFailure( + rootCause( + instanceOf(IllegalStateException.class), + message(""" + Failed to find a static test bean factory method in %s with return type \ + java.lang.String whose name matches one of the supported candidates \ + [notPresent]""".formatted(testClass.getName()))))); } @Test void testBeanFailingNoExplicitMethod() { Class testClass = ImplicitTestOverrideMethodNotPresentTestCase.class; - EngineExecutionResults results = EngineTestKit.engine("junit-jupiter")// - .selectors(selectClass(testClass))// - .execute(); - - assertThat(results.allEvents().failed().stream()).hasSize(1).first() - .satisfies(e -> assertThat(e.getRequiredPayload(TestExecutionResult.class) - .getThrowable()).get(THROWABLE) - .rootCause().isInstanceOf(IllegalStateException.class) - .hasMessage("Failed to find a static test bean factory method in %s " + - "with return type java.lang.String whose name matches one of the " + - "supported candidates [fieldTestOverride]", testClass.getName())); + EngineTestKitUtils.executeTestsForClass(testClass).assertThatEvents().haveExactly(1, + finishedWithFailure( + rootCause( + instanceOf(IllegalStateException.class), + message(""" + Failed to find a static test bean factory method in %s with return type \ + java.lang.String whose name matches one of the supported candidates \ + [fieldTestOverride]""".formatted(testClass.getName()))))); } @Test void testBeanFailingBeanOfWrongType() { - EngineExecutionResults results = EngineTestKit.engine("junit-jupiter")// - .selectors(selectClass(BeanTypeMismatchTestCase.class))// - .execute(); - - assertThat(results.allEvents().failed().stream()).hasSize(1).first() - .satisfies(e -> assertThat(e.getRequiredPayload(TestExecutionResult.class) - .getThrowable()).get(THROWABLE) - .rootCause().isInstanceOf(IllegalStateException.class) - .hasMessage("Unable to override bean 'notString'; there is no bean definition to replace with " + - "that name of type java.lang.String")); + Class testClass = BeanTypeMismatchTestCase.class; + EngineTestKitUtils.executeTestsForClass(testClass).assertThatEvents().haveExactly(1, + finishedWithFailure( + rootCause( + instanceOf(IllegalStateException.class), + message(""" + Unable to override bean 'notString': there is no bean definition to replace \ + with that name of type java.lang.String""")))); } - @Configuration - static class Config { - - @Bean("field") - String bean1() { - return "prod"; - } - - @Bean("nestedField") - String bean2() { - return "nestedProd"; - } - - @Bean("methodRenamed1") - String bean3() { - return "Prod"; - } - - @Bean("methodRenamed2") - String bean4() { - return "NestedProd"; - } - } @SpringJUnitConfig - @DisabledInAotMode static class NoOriginalBeanTestCase { @TestBean(name = "noOriginalBean") String noOriginalBean; @Test - void ignored() { + void test() { fail("should fail earlier"); } static String noOriginalBeanTestOverride() { return "should be ignored"; } - } @SpringJUnitConfig - @DisabledInAotMode static class BeanDefinitionToOverrideNotPresentTestCase { @TestBean(name = "notPresent") String field; @Test - void ignored() { + void test() { fail("should fail earlier"); } @@ -175,40 +136,37 @@ static String notPresentTestOverride() { } @SpringJUnitConfig - @DisabledInAotMode static class ExplicitTestOverrideMethodNotPresentTestCase { @TestBean(methodName = "notPresent") String field; @Test - void ignored() { + void test() { fail("should fail earlier"); } } @SpringJUnitConfig - @DisabledInAotMode static class ImplicitTestOverrideMethodNotPresentTestCase { - @TestBean //expects fieldTestOverride method + @TestBean // expects fieldTestOverride method String field; @Test - void ignored() { + void test() { fail("should fail earlier"); } } @SpringJUnitConfig - @DisabledInAotMode static class BeanTypeMismatchTestCase { @TestBean(name = "notString") String field; @Test - void ignored() { + void test() { fail("should fail earlier"); } diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/convention/TestBeanByTypeIntegrationTests.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/convention/TestBeanByTypeIntegrationTests.java index 7f291d697603..70003428a325 100644 --- a/spring-test/src/test/java/org/springframework/test/context/bean/override/convention/TestBeanByTypeIntegrationTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/bean/override/convention/TestBeanByTypeIntegrationTests.java @@ -17,9 +17,6 @@ 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.beans.factory.annotation.Qualifier; import org.springframework.context.ApplicationContext; @@ -31,11 +28,16 @@ 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) +/** + * {@link TestBean @TestBean} "by type" integration tests for success scenarios. + * + * @author Simon Baslé + * @author Sam Brannen + * @since 6.2 + * @see FailingTestBeanByTypeIntegrationTests + */ +@SpringJUnitConfig public class TestBeanByTypeIntegrationTests { @TestBean @@ -61,6 +63,7 @@ static StringBuilder someString2() { return new StringBuilder("CustomQualifier TestBean String"); } + @Test void overrideIsFoundByType(ApplicationContext ctx) { assertThat(this.anyNameForService) @@ -85,42 +88,10 @@ void overrideIsFoundByTypeWithQualifierDisambiguation(ApplicationContext ctx) { assertThat(ctx.getBean("one")).as("no qualifier needed").hasToString("Prod One"); } - @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 { + static class Config { + @Bean("example") ExampleService bean1() { return new RealExampleService("Production hello"); @@ -144,47 +115,4 @@ StringBuilder beanString3() { } } - @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"); - } - } } diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/convention/TestBeanInheritanceIntegrationTests.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/convention/TestBeanInheritanceIntegrationTests.java index 4879026eaecb..22ff82cdca33 100644 --- a/spring-test/src/test/java/org/springframework/test/context/bean/override/convention/TestBeanInheritanceIntegrationTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/bean/override/convention/TestBeanInheritanceIntegrationTests.java @@ -19,30 +19,33 @@ import org.junit.jupiter.api.DisplayName; 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.springframework.context.ApplicationContext; 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; +/** + * {@link TestBean @TestBean} inheritance integration tests for success scenarios. + * + * @author Simon Baslé + * @author Sam Brannen + * @since 6.2 + * @see FailingTestBeanInheritanceIntegrationTests + */ public class TestBeanInheritanceIntegrationTests { - static AbstractTestBeanIntegrationTestCase.Pojo nestedBeanOverride() { + static AbstractTestBeanIntegrationTestCase.Pojo enclosingClassBeanOverride() { return new AbstractTestBeanIntegrationTestCase.FakePojo("in enclosing test class"); } @Nested @DisplayName("Concrete inherited test with correct @TestBean setup") - class ConcreteTestBeanIntegrationTests extends AbstractTestBeanIntegrationTestCase { + public class ConcreteTestBeanIntegrationTests extends AbstractTestBeanIntegrationTestCase { @TestBean(name = "pojo", methodName = "commonBeanOverride") Pojo pojo; - @TestBean(name = "pojo2", methodName = "nestedBeanOverride") + @TestBean(name = "pojo2", methodName = "enclosingClassBeanOverride") Pojo pojo2; static Pojo someBeanTestOverride() { @@ -74,60 +77,4 @@ void fieldInSupertypePrioritizeMethodInType(ApplicationContext ctx) { } } - - @Test - void failsIfFieldInSupertypeButNoMethod() { - Class clazz = Failing1.class; - EngineExecutionResults results = EngineTestKit.engine("junit-jupiter")// - .selectors(selectClass(clazz))// - .execute(); - - assertThat(results.allEvents().failed().stream()).hasSize(1).first() - .satisfies(e -> assertThat(e.getRequiredPayload(TestExecutionResult.class) - .getThrowable()).get(THROWABLE) - .rootCause().isInstanceOf(IllegalStateException.class) - .hasMessage(""" - Failed to find a static test bean factory method in %s with return type %s \ - whose name matches one of the supported candidates [someBeanTestOverride]""", - clazz.getName(), AbstractTestBeanIntegrationTestCase.Pojo.class.getName())); - } - - @Test - void failsIfMethod1InSupertypeAndMethod2InType() { - Class clazz = Failing2.class; - EngineExecutionResults results = EngineTestKit.engine("junit-jupiter")// - .selectors(selectClass(clazz)) - .execute(); - - assertThat(results.allEvents().failed().stream()).hasSize(1).first() - .satisfies(e -> assertThat(e.getRequiredPayload(TestExecutionResult.class) - .getThrowable()).get(THROWABLE) - .rootCause().isInstanceOf(IllegalStateException.class) - .hasMessage(""" - Found 2 competing static test bean factory methods in %s with return type %s \ - whose name matches one of the supported candidates \ - [thirdBeanTestOverride, anotherBeanTestOverride]""", - clazz.getName(), AbstractTestBeanIntegrationTestCase.Pojo.class.getName())); - } - - static class Failing1 extends AbstractTestBeanIntegrationTestCase { - - @Test - void ignored() { - } - } - - static class Failing2 extends AbstractTestBeanIntegrationTestCase { - - static Pojo someBeanTestOverride() { - return new FakePojo("ignored"); - } - - static Pojo anotherBeanTestOverride() { - return new FakePojo("sub2"); - } - - @Test - void ignored2() { } - } } diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/convention/TestBeanIntegrationTests.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/convention/TestBeanIntegrationTests.java index 87b8a74cdc66..f6020809de2a 100644 --- a/spring-test/src/test/java/org/springframework/test/context/bean/override/convention/TestBeanIntegrationTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/bean/override/convention/TestBeanIntegrationTests.java @@ -30,6 +30,8 @@ /** * {@link TestBean @TestBean} integration tests for success scenarios. * + * @author Simon Baslé + * @author Sam Brannen * @since 6.2 * @see FailingTestBeanIntegrationTests */ @@ -82,8 +84,8 @@ void fieldWithMethodNameHasOverride(ApplicationContext ctx) { @Nested - @DisplayName("With @TestBean on enclosing class") - class TestBeanNested { + @DisplayName("With @TestBean in enclosing class") + public class TestBeanNestedTests { @Test void fieldHasOverride(ApplicationContext ctx) { @@ -105,8 +107,8 @@ void fieldWithMethodNameHasOverride(ApplicationContext ctx) { } @Nested - @DisplayName("With factory method on enclosing class") - class TestBeanNested2 { + @DisplayName("With factory method in enclosing class") + public class TestBeanNested2Tests { @TestBean(methodName = "nestedFieldTestOverride", name = "nestedField") String nestedField2; diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/FailingMockitoBeanByTypeIntegrationTests.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/FailingMockitoBeanByTypeIntegrationTests.java new file mode 100644 index 000000000000..34636d51bd46 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/FailingMockitoBeanByTypeIntegrationTests.java @@ -0,0 +1,113 @@ +/* + * 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.Test; + +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.EngineTestKitUtils; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.platform.testkit.engine.EventConditions.finishedWithFailure; +import static org.junit.platform.testkit.engine.TestExecutionResultConditions.cause; +import static org.junit.platform.testkit.engine.TestExecutionResultConditions.instanceOf; +import static org.junit.platform.testkit.engine.TestExecutionResultConditions.message; + +/** + * {@link MockitoBean @MockitoBean} "by type" integration tests for failure scenarios. + * + * @author Simon Baslé + * @author Sam Brannen + * @since 6.2 + * @see MockitoBeanByTypeIntegrationTests + */ +class FailingMockitoBeanByTypeIntegrationTests { + + @Test + void zeroCandidates() { + Class testClass = ZeroCandidatesTestCase.class; + EngineTestKitUtils.executeTestsForClass(testClass).assertThatEvents().haveExactly(1, + finishedWithFailure( + cause( + instanceOf(IllegalStateException.class), + message(""" + Unable to select a bean definition to override: 0 bean definitions \ + found of type %s (as required by annotated field '%s.example')""" + .formatted(ExampleService.class.getName(), testClass.getSimpleName()))))); + } + + @Test + void tooManyCandidates() { + Class testClass = TooManyCandidatesTestCase.class; + EngineTestKitUtils.executeTestsForClass(testClass).assertThatEvents().haveExactly(1, + finishedWithFailure( + cause( + instanceOf(IllegalStateException.class), + message(""" + Unable to select a bean definition to override: 2 bean definitions \ + found of type %s (as required by annotated field '%s.example')""" + .formatted(ExampleService.class.getName(), testClass.getSimpleName()))))); + } + + + @SpringJUnitConfig + static class ZeroCandidatesTestCase { + + @MockitoBean + ExampleService example; + + @Test + void test() { + assertThat(example).isNotNull(); + } + + @Configuration + static class Config { + } + } + + @SpringJUnitConfig + static class TooManyCandidatesTestCase { + + @MockitoBean + ExampleService example; + + @Test + void test() { + assertThat(example).isNotNull(); + } + + @Configuration + static class Config { + + @Bean + ExampleService bean1() { + return new RealExampleService("1 Hello"); + } + + @Bean + ExampleService bean2() { + return new RealExampleService("2 Hello"); + } + } + } + +} diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/FailingMockitoSpyBeanByTypeIntegrationTests.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/FailingMockitoSpyBeanByTypeIntegrationTests.java new file mode 100644 index 000000000000..3eac5dbe8dc2 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/FailingMockitoSpyBeanByTypeIntegrationTests.java @@ -0,0 +1,112 @@ +/* + * 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.Test; + +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.EngineTestKitUtils; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.platform.testkit.engine.EventConditions.finishedWithFailure; +import static org.junit.platform.testkit.engine.TestExecutionResultConditions.cause; +import static org.junit.platform.testkit.engine.TestExecutionResultConditions.instanceOf; +import static org.junit.platform.testkit.engine.TestExecutionResultConditions.message; + +/** + * {@link MockitoSpyBean @MockitoSpyBean} "by type" integration tests for failure scenarios. + * + * @author Sam Brannen + * @since 6.2 + * @see MockitoSpyBeanByTypeIntegrationTests + */ +class FailingMockitoSpyBeanByTypeIntegrationTests { + + @Test + void zeroCandidates() { + Class testClass = ZeroCandidatesTestCase.class; + EngineTestKitUtils.executeTestsForClass(testClass).assertThatEvents().haveExactly(1, + finishedWithFailure( + cause( + instanceOf(IllegalStateException.class), + message(""" + Unable to select a bean to override by wrapping: 0 bean instances found of \ + type %s (as required by annotated field '%s.example')""" + .formatted(ExampleService.class.getName(), testClass.getSimpleName()))))); + } + + @Test + void tooManyCandidates() { + Class testClass = TooManyCandidatesTestCase.class; + EngineTestKitUtils.executeTestsForClass(testClass).assertThatEvents().haveExactly(1, + finishedWithFailure( + cause( + instanceOf(IllegalStateException.class), + message(""" + Unable to select a bean to override by wrapping: 2 bean instances found of \ + type %s (as required by annotated field '%s.example')""" + .formatted(ExampleService.class.getName(), testClass.getSimpleName()))))); + } + + + @SpringJUnitConfig + static class ZeroCandidatesTestCase { + + @MockitoSpyBean + ExampleService example; + + @Test + void test() { + assertThat(example).isNotNull(); + } + + @Configuration + static class Config { + } + } + + @SpringJUnitConfig + static class TooManyCandidatesTestCase { + + @MockitoSpyBean + ExampleService example; + + @Test + void test() { + assertThat(example).isNotNull(); + } + + @Configuration + static class Config { + + @Bean + ExampleService bean1() { + return new RealExampleService("1 Hello"); + } + + @Bean + ExampleService bean2() { + return new RealExampleService("2 Hello"); + } + } + } + +} diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/FailingMockitoSpyBeanIntegrationTests.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/FailingMockitoSpyBeanIntegrationTests.java new file mode 100644 index 000000000000..dbebac956e38 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/FailingMockitoSpyBeanIntegrationTests.java @@ -0,0 +1,87 @@ +/* + * 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.Test; + +import org.springframework.test.context.bean.override.example.ExampleService; +import org.springframework.test.context.junit.EngineTestKitUtils; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; + +import static org.junit.platform.testkit.engine.EventConditions.finishedWithFailure; +import static org.junit.platform.testkit.engine.TestExecutionResultConditions.cause; +import static org.junit.platform.testkit.engine.TestExecutionResultConditions.instanceOf; +import static org.junit.platform.testkit.engine.TestExecutionResultConditions.message; + +/** + * {@link MockitoSpyBean @MockitoSpyBean} integration tests for failure scenarios. + * + * @author Simon Baslé + * @author Sam Brannen + * @since 6.2 + * @see MockitoSpyBeanIntegrationTests + */ +class FailingMockitoSpyBeanIntegrationTests { + + @Test + void failWhenBeanNotPresentByType() { + Class testClass = BeanNotPresentByTypeTestCase.class; + EngineTestKitUtils.executeTestsForClass(testClass).assertThatEvents().haveExactly(1, + finishedWithFailure( + cause(instanceOf(IllegalStateException.class), + message(""" + Unable to select a bean to override by wrapping: 0 bean instances found \ + of type %s (as required by annotated field '%s.notPresent')""" + .formatted(ExampleService.class.getName(), testClass.getSimpleName()))))); + } + + @Test + void failWhenBeanNotPresentByExplicitName() { + Class testClass = BeanNotPresentByExplicitNameTestCase.class; + EngineTestKitUtils.executeTestsForClass(testClass).assertThatEvents().haveExactly(1, + finishedWithFailure( + cause(instanceOf(IllegalStateException.class), + message(""" + Unable to override bean 'notPresentAtAll' by wrapping: \ + there is no existing bean instance with that name of type %s""" + .formatted(ExampleService.class.getName()))))); + } + + + @SpringJUnitConfig + static class BeanNotPresentByTypeTestCase { + + @MockitoSpyBean + ExampleService notPresent; + + @Test + void test() { + } + } + + @SpringJUnitConfig + static class BeanNotPresentByExplicitNameTestCase { + + @MockitoSpyBean(name = "notPresentAtAll") + ExampleService field; + + @Test + void test() { + } + } + +} diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoBeanByTypeIntegrationTests.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoBeanByTypeIntegrationTests.java new file mode 100644 index 000000000000..2aa512b24f91 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoBeanByTypeIntegrationTests.java @@ -0,0 +1,135 @@ +/* + * 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.Test; +import org.mockito.Mockito; + +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.annotation.Order; +import org.springframework.test.context.bean.override.example.CustomQualifier; +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.assertThatException; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.BDDMockito.when; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +/** + * {@link MockitoBean @MockitoBean} "by type" integration tests for success scenarios. + * + * @author Simon Baslé + * @author Sam Brannen + * @since 6.2 + * @see FailingMockitoBeanByTypeIntegrationTests + */ +@SpringJUnitConfig +public class MockitoBeanByTypeIntegrationTests { + + @MockitoBean + ExampleService anyNameForService; + + @MockitoBean + @Qualifier("prefer") + StringBuilder ambiguous; + + @MockitoBean + @CustomQualifier + StringBuilder ambiguousMeta; + + + @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 overrideIsFoundByTypeAndDisambiguatedByQualifier(ApplicationContext ctx) { + assertThat(this.ambiguous) + .satisfies(o -> assertThat(Mockito.mockingDetails(o).isMock()).as("isMock").isTrue()) + .isSameAs(ctx.getBean("ambiguous2")); + + assertThatException() + .isThrownBy(() -> ctx.getBean(StringBuilder.class)) + .withMessageEndingWith("but found 2: ambiguous2,ambiguous1"); + + assertThat(this.ambiguous).isEmpty(); + assertThat(this.ambiguous.substring(0)).isNull(); + verify(this.ambiguous, times(1)).length(); + verify(this.ambiguous, times(1)).substring(anyInt()); + verifyNoMoreInteractions(this.ambiguous); + } + + @Test + void overrideIsFoundByTypeAndDisambiguatedByMetaQualifier(ApplicationContext ctx) { + assertThat(this.ambiguousMeta) + .satisfies(o -> assertThat(Mockito.mockingDetails(o).isMock()).as("isMock").isTrue()) + .isSameAs(ctx.getBean("ambiguous1")); + + assertThatException() + .isThrownBy(() -> ctx.getBean(StringBuilder.class)) + .withMessageEndingWith("but found 2: ambiguous2,ambiguous1"); + + assertThat(this.ambiguousMeta).isEmpty(); + assertThat(this.ambiguousMeta.substring(0)).isNull(); + verify(this.ambiguousMeta, times(1)).length(); + verify(this.ambiguousMeta, times(1)).substring(anyInt()); + verifyNoMoreInteractions(this.ambiguousMeta); + } + + + @Configuration + static class Config { + + @Bean("example") + ExampleService bean1() { + return new RealExampleService("Production hello"); + } + + @Bean("ambiguous1") + @Order(1) + @CustomQualifier + StringBuilder bean2() { + return new StringBuilder("bean2"); + } + + @Bean("ambiguous2") + @Order(2) + @Qualifier("prefer") + StringBuilder bean3() { + return new StringBuilder("bean3"); + } + } + +} diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoBeanIntegrationTests.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoBeanIntegrationTests.java index 76670401336f..007134a00c9a 100644 --- a/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoBeanIntegrationTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoBeanIntegrationTests.java @@ -51,12 +51,12 @@ public class MockitoBeanIntegrationTests { @MockitoBean(name = "nestedNonExistingBean") ExampleService nonExisting2; + @Test void fieldHasOverride(ApplicationContext ctx) { assertThat(ctx.getBean("field")) .isInstanceOf(ExampleService.class) - .satisfies(o -> assertThat(Mockito.mockingDetails(o).isMock()) - .as("isMock").isTrue()) + .satisfies(o -> assertThat(Mockito.mockingDetails(o).isMock()).as("isMock").isTrue()) .isSameAs(this.field); assertThat(this.field.greeting()).as("mocked greeting").isNull(); @@ -66,8 +66,7 @@ void fieldHasOverride(ApplicationContext ctx) { void renamedFieldHasOverride(ApplicationContext ctx) { assertThat(ctx.getBean("field")) .isInstanceOf(ExampleService.class) - .satisfies(o -> assertThat(Mockito.mockingDetails(o).isMock()) - .as("isMock").isTrue()) + .satisfies(o -> assertThat(Mockito.mockingDetails(o).isMock()).as("isMock").isTrue()) .isSameAs(this.renamed1); assertThat(this.renamed1.greeting()).as("mocked greeting").isNull(); @@ -77,23 +76,22 @@ void renamedFieldHasOverride(ApplicationContext ctx) { void fieldIsMockedWhenNoOriginalBean(ApplicationContext ctx) { assertThat(ctx.getBean("nonExistingBean")) .isInstanceOf(ExampleService.class) - .satisfies(o -> assertThat(Mockito.mockingDetails(o).isMock()) - .as("isMock").isTrue()) + .satisfies(o -> assertThat(Mockito.mockingDetails(o).isMock()).as("isMock").isTrue()) .isSameAs(this.nonExisting1); assertThat(this.nonExisting1.greeting()).as("mocked greeting").isNull(); } + @Nested - @DisplayName("With @MockitoBean on enclosing class") - class MockitoBeanNested { + @DisplayName("With @MockitoBean in enclosing class") + public class MockitoBeanNestedTests { @Test void fieldHasOverride(ApplicationContext ctx) { assertThat(ctx.getBean("nestedField")) .isInstanceOf(ExampleService.class) - .satisfies(o -> assertThat(Mockito.mockingDetails(o).isMock()) - .as("isMock").isTrue()) + .satisfies(o -> assertThat(Mockito.mockingDetails(o).isMock()).as("isMock").isTrue()) .isSameAs(MockitoBeanIntegrationTests.this.nestedField); } @@ -101,8 +99,7 @@ void fieldHasOverride(ApplicationContext ctx) { void renamedFieldHasOverride(ApplicationContext ctx) { assertThat(ctx.getBean("nestedField")) .isInstanceOf(ExampleService.class) - .satisfies(o -> assertThat(Mockito.mockingDetails(o).isMock()) - .as("isMock").isTrue()) + .satisfies(o -> assertThat(Mockito.mockingDetails(o).isMock()).as("isMock").isTrue()) .isSameAs(MockitoBeanIntegrationTests.this.renamed2); } @@ -110,8 +107,7 @@ void renamedFieldHasOverride(ApplicationContext ctx) { void fieldIsMockedWhenNoOriginalBean(ApplicationContext ctx) { assertThat(ctx.getBean("nestedNonExistingBean")) .isInstanceOf(ExampleService.class) - .satisfies(o -> assertThat(Mockito.mockingDetails(o).isMock()) - .as("isMock").isTrue()) + .satisfies(o -> assertThat(Mockito.mockingDetails(o).isMock()).as("isMock").isTrue()) .isSameAs(MockitoBeanIntegrationTests.this.nonExisting2); } } @@ -119,13 +115,16 @@ void fieldIsMockedWhenNoOriginalBean(ApplicationContext ctx) { @Configuration static class Config { + @Bean("field") ExampleService bean1() { return new RealExampleService("Hello Field"); } + @Bean("nestedField") ExampleService bean2() { return new RealExampleService("Hello Nested Field"); } } + } diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoByTypeIntegrationTests.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoByTypeIntegrationTests.java deleted file mode 100644 index b4491b8b813d..000000000000 --- a/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoByTypeIntegrationTests.java +++ /dev/null @@ -1,323 +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 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.beans.factory.annotation.Qualifier; -import org.springframework.context.ApplicationContext; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.core.annotation.Order; -import org.springframework.test.context.bean.override.example.CustomQualifier; -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.assertThatException; -import static org.assertj.core.api.InstanceOfAssertFactories.THROWABLE; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; -import static org.mockito.ArgumentMatchers.anyInt; -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; - - @MockitoBean - @Qualifier("prefer") - StringBuilder ambiguous; - - @MockitoBean - @CustomQualifier - StringBuilder ambiguousMeta; - - @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 overrideIsFoundByTypeAndDisambiguatedByQualifier(ApplicationContext ctx) { - assertThat(this.ambiguous) - .satisfies(o -> assertThat(Mockito.mockingDetails(o).isMock()) - .as("isMock").isTrue()) - .isSameAs(ctx.getBean("ambiguous2")); - - assertThatException().isThrownBy(() -> ctx.getBean(StringBuilder.class)) - .withMessageEndingWith("but found 2: ambiguous2,ambiguous1"); - - assertThat(this.ambiguous.length()).isZero(); - assertThat(this.ambiguous.substring(0)).isNull(); - verify(this.ambiguous, times(1)).length(); - verify(this.ambiguous, times(1)).substring(anyInt()); - verifyNoMoreInteractions(this.ambiguous); - } - - @Test - void overrideIsFoundByTypeAndDisambiguatedByMetaQualifier(ApplicationContext ctx) { - assertThat(this.ambiguousMeta) - .satisfies(o -> assertThat(Mockito.mockingDetails(o).isMock()) - .as("isMock").isTrue()) - .isSameAs(ctx.getBean("ambiguous1")); - - assertThatException().isThrownBy(() -> ctx.getBean(StringBuilder.class)) - .withMessageEndingWith("but found 2: ambiguous2,ambiguous1"); - - assertThat(this.ambiguousMeta.length()).isZero(); - assertThat(this.ambiguousMeta.substring(0)).isNull(); - verify(this.ambiguousMeta, times(1)).length(); - verify(this.ambiguousMeta, times(1)).substring(anyInt()); - verifyNoMoreInteractions(this.ambiguousMeta); - } - - @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; - - @MockitoSpyBean - @Qualifier("prefer") - StringBuilder ambiguous; - - @MockitoSpyBean - @CustomQualifier - StringBuilder ambiguousMeta; - - @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 overrideIsFoundByTypeAndDisambiguatedByQualifier(ApplicationContext ctx) { - assertThat(this.ambiguous) - .satisfies(o -> assertThat(Mockito.mockingDetails(o).isSpy()) - .as("isSpy").isTrue()) - .isSameAs(ctx.getBean("ambiguous2")); - - assertThatException().isThrownBy(() -> ctx.getBean(StringBuilder.class)) - .withMessageEndingWith("but found 2: ambiguous1,ambiguous2"); - - assertThat(this.ambiguous.toString()).isEqualTo("bean3"); - assertThat(this.ambiguous.length()).isEqualTo(5); - verify(this.ambiguous, times(1)).length(); - verifyNoMoreInteractions(this.ambiguous); //mockito doesn't verify toString - } - - @Test - void overrideIsFoundByTypeAndDisambiguatedByMetaQualifier(ApplicationContext ctx) { - assertThat(this.ambiguousMeta) - .satisfies(o -> assertThat(Mockito.mockingDetails(o).isSpy()) - .as("isSpy").isTrue()) - .isSameAs(ctx.getBean("ambiguous1")); - - assertThatException().isThrownBy(() -> ctx.getBean(StringBuilder.class)) - .withMessageEndingWith("but found 2: ambiguous1,ambiguous2"); - - assertThat(this.ambiguousMeta.toString()).isEqualTo("bean2"); - assertThat(this.ambiguousMeta.length()).isEqualTo(5); - verify(this.ambiguousMeta, times(1)).length(); - verifyNoMoreInteractions(this.ambiguousMeta); //mockito doesn't verify toString - } - - @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"); - } - - @Bean("ambiguous1") - @Order(1) - @CustomQualifier - StringBuilder bean2() { - return new StringBuilder("bean2"); - } - - @Bean("ambiguous2") - @Order(2) - @Qualifier("prefer") - StringBuilder bean3() { - return new StringBuilder("bean3"); - } - } - - @Configuration - static class FailingNone { - } - - @Configuration - static class FailingTooMany { - @Bean - ExampleService bean1() { - return new RealExampleService("1 Hello"); - } - @Bean - ExampleService bean2() { - return new RealExampleService("2 Hello"); - } - } -} diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoSpyBeanByTypeIntegrationTests.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoSpyBeanByTypeIntegrationTests.java new file mode 100644 index 000000000000..362e7310f757 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoSpyBeanByTypeIntegrationTests.java @@ -0,0 +1,129 @@ +/* + * 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.Test; +import org.mockito.Mockito; + +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.annotation.Order; +import org.springframework.test.context.bean.override.example.CustomQualifier; +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.assertThatException; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +/** + * {@link MockitoSpyBean @MockitoSpyBean} "by type" integration tests for success scenarios. + * + * @author Simon Baslé + * @author Sam Brannen + * @since 6.2 + * @see FailingMockitoSpyBeanByTypeIntegrationTests + */ +@SpringJUnitConfig +public class MockitoSpyBeanByTypeIntegrationTests { + + @MockitoSpyBean + ExampleService anyNameForService; + + @MockitoSpyBean + @Qualifier("prefer") + StringBuilder ambiguous; + + @MockitoSpyBean + @CustomQualifier + StringBuilder ambiguousMeta; + + + @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 overrideIsFoundByTypeAndDisambiguatedByQualifier(ApplicationContext ctx) { + assertThat(this.ambiguous) + .satisfies(o -> assertThat(Mockito.mockingDetails(o).isSpy()).as("isSpy").isTrue()) + .isSameAs(ctx.getBean("ambiguous2")); + + assertThatException() + .isThrownBy(() -> ctx.getBean(StringBuilder.class)) + .withMessageEndingWith("but found 2: ambiguous1,ambiguous2"); + + assertThat(this.ambiguous.toString()).isEqualTo("bean3"); + assertThat(this.ambiguous.length()).isEqualTo(5); + verify(this.ambiguous, times(1)).length(); + verifyNoMoreInteractions(this.ambiguous); //mockito doesn't verify toString + } + + @Test + void overrideIsFoundByTypeAndDisambiguatedByMetaQualifier(ApplicationContext ctx) { + assertThat(this.ambiguousMeta) + .satisfies(o -> assertThat(Mockito.mockingDetails(o).isSpy()).as("isSpy").isTrue()) + .isSameAs(ctx.getBean("ambiguous1")); + + assertThatException() + .isThrownBy(() -> ctx.getBean(StringBuilder.class)) + .withMessageEndingWith("but found 2: ambiguous1,ambiguous2"); + + assertThat(this.ambiguousMeta.toString()).isEqualTo("bean2"); + assertThat(this.ambiguousMeta.length()).isEqualTo(5); + verify(this.ambiguousMeta, times(1)).length(); + verifyNoMoreInteractions(this.ambiguousMeta); //mockito doesn't verify toString + } + + + @Configuration + static class Config { + + @Bean("example") + ExampleService bean1() { + return new RealExampleService("Production hello"); + } + + @Bean("ambiguous1") + @Order(1) + @CustomQualifier + StringBuilder bean2() { + return new StringBuilder("bean2"); + } + + @Bean("ambiguous2") + @Order(2) + @Qualifier("prefer") + StringBuilder bean3() { + return new StringBuilder("bean3"); + } + } + +} diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoSpyBeanIntegrationTests.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoSpyBeanIntegrationTests.java index 24c0076d037b..7c00af6f9627 100644 --- a/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoSpyBeanIntegrationTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoSpyBeanIntegrationTests.java @@ -19,9 +19,6 @@ import org.junit.jupiter.api.DisplayName; 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; @@ -29,9 +26,14 @@ 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; +/** + * {@link MockitoSpyBean @MockitoSpyBean} integration tests for success scenarios. + * + * @author Simon Baslé + * @since 6.2 + * @see FailingMockitoSpyBeanIntegrationTests + */ @SpringJUnitConfig(MockitoBeanIntegrationTests.Config.class) public class MockitoSpyBeanIntegrationTests { @@ -47,73 +49,36 @@ public class MockitoSpyBeanIntegrationTests { @MockitoSpyBean(name = "nestedField") ExampleService renamed2; + @Test void fieldHasOverride(ApplicationContext ctx) { assertThat(ctx.getBean("field")) .isInstanceOf(ExampleService.class) - .satisfies(o -> assertThat(Mockito.mockingDetails(o).isSpy()) - .as("isSpy").isTrue()) + .satisfies(o -> assertThat(Mockito.mockingDetails(o).isSpy()).as("isSpy").isTrue()) .isSameAs(this.field); - assertThat(this.field.greeting()).as("spied greeting") - .isEqualTo("Hello Field"); + assertThat(this.field.greeting()).as("spied greeting").isEqualTo("Hello Field"); } @Test void renamedFieldHasOverride(ApplicationContext ctx) { assertThat(ctx.getBean("field")) .isInstanceOf(ExampleService.class) - .satisfies(o -> assertThat(Mockito.mockingDetails(o).isSpy()) - .as("isSpy").isTrue()) + .satisfies(o -> assertThat(Mockito.mockingDetails(o).isSpy()).as("isSpy").isTrue()) .isSameAs(this.renamed1); - assertThat(this.field.greeting()).as("spied greeting") - .isEqualTo("Hello Field"); - } - - @Test - void failWhenBeanNotPresentByType() { - EngineExecutionResults results = EngineTestKit.engine("junit-jupiter")// - .selectors(selectClass(Failure1.class))// - .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 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 failWhenBeanNotPresentByExplicitName() { - EngineExecutionResults results = EngineTestKit.engine("junit-jupiter")// - .selectors(selectClass(Failure2.class))// - .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 override bean 'notPresentAtAll' by wrapping; " + - "there is no existing bean instance with that name of type %s", - ExampleService.class.getName())); + assertThat(this.renamed1.greeting()).as("spied greeting").isEqualTo("Hello Field"); } @Nested - @DisplayName("With @MockitoSpyBean on enclosing class") - class MockitoBeanNested { + @DisplayName("With @MockitoSpyBean in enclosing class") + public class MockitoSpyBeanNestedTests { @Test void fieldHasOverride(ApplicationContext ctx) { assertThat(ctx.getBean("nestedField")) .isInstanceOf(ExampleService.class) - .satisfies(o -> assertThat(Mockito.mockingDetails(o).isSpy()) - .as("isSpy").isTrue()) + .satisfies(o -> assertThat(Mockito.mockingDetails(o).isSpy()).as("isSpy").isTrue()) .isSameAs(MockitoSpyBeanIntegrationTests.this.nestedField); assertThat(MockitoSpyBeanIntegrationTests.this.nestedField.greeting()) @@ -125,36 +90,13 @@ void fieldHasOverride(ApplicationContext ctx) { void renamedFieldHasOverride(ApplicationContext ctx) { assertThat(ctx.getBean("nestedField")) .isInstanceOf(ExampleService.class) - .satisfies(o -> assertThat(Mockito.mockingDetails(o).isSpy()) - .as("isSpy").isTrue()) + .satisfies(o -> assertThat(Mockito.mockingDetails(o).isSpy()).as("isSpy").isTrue()) .isSameAs(MockitoSpyBeanIntegrationTests.this.renamed2); - assertThat(MockitoSpyBeanIntegrationTests.this.renamed2.greeting()) .as("spied greeting") .isEqualTo("Hello Nested Field"); } } - - @SpringJUnitConfig - static class Failure1 { - - @MockitoSpyBean - ExampleService notPresent; - - @Test - void ignored() { } - - } - - @SpringJUnitConfig - static class Failure2 { - - @MockitoSpyBean(name = "notPresentAtAll") - ExampleService field; - - @Test - void ignored() { } - } } diff --git a/spring-test/src/test/java/org/springframework/test/context/junit/EngineTestKitUtils.java b/spring-test/src/test/java/org/springframework/test/context/junit/EngineTestKitUtils.java new file mode 100644 index 000000000000..f66ed13aa37b --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/junit/EngineTestKitUtils.java @@ -0,0 +1,68 @@ +/* + * 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.junit; + +import java.util.Arrays; +import java.util.List; + +import org.assertj.core.api.Assertions; +import org.assertj.core.api.Condition; +import org.junit.platform.testkit.engine.EngineTestKit; +import org.junit.platform.testkit.engine.Events; + +import static java.util.stream.Collectors.toList; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; + +/** + * Utilities for tests that use JUnit's {@link EngineTestKit}. + * + * @author Sam Brannen + * @since 6.2 + */ +public class EngineTestKitUtils { + + public static Events executeTestsForClass(Class testClass) { + return EngineTestKit.engine("junit-jupiter") + .selectors(selectClass(testClass)) + .execute() + .testEvents(); + } + + @SafeVarargs + @SuppressWarnings("varargs") + public static Condition rootCause(Condition... conditions) { + List> list = Arrays.stream(conditions) + .map(EngineTestKitUtils::rootCause) + .collect(toList()); + + return Assertions.allOf(list); + } + + private static Condition rootCause(Condition condition) { + return new Condition<>(throwable -> condition.matches(getRootCause(throwable)), + "throwable root cause matches %s", condition); + } + + private static Throwable getRootCause(Throwable throwable) { + Throwable cause = throwable.getCause(); + if (cause == null) { + return throwable; + } + return getRootCause(cause); + } + +}