Revise bean override tests with a focus on AOT support

As a follow up to the previous commit (31f8e12adb), this commit
polishes bean override tests and revises them with a focus on AOT
testing support and simplified maintenance.

- Introduce EngineTestKitUtils to simplify working with JUnit's
  EngineTestKit.

- Use idiomatic EngineTestKit APIs to simplify assertions on
  EngineTestKit results.

- Introduce BeanOverrideTestSuite to simplify running all bean override
  tests within the IDE.

- Separate failure and success scenario tests, so that failure tests do
  not launch the JUnit Platform to run tests using the Spring
  TestContext Framework (TCF) within a test class that itself uses the
  TCF.

- Make AbstractTestBeanIntegrationTestCase actually abstract.

- Rename test case classes to give them meaningful names and simplify
  understanding of what's being tested.

- Ensure tests for @⁠MockitoSpyBean functionality use @⁠MockitoSpyBean
  instead of @⁠MockitoBean.

- Declare @⁠Configuration classes local to @⁠SpringJUnitConfig test
  classes whenever possible.

See gh-29122
See gh-32925
This commit is contained in:
Sam Brannen 2024-05-31 11:41:50 +02:00
parent 31f8e12adb
commit 87c93d35d6
19 changed files with 1044 additions and 681 deletions

View File

@ -133,7 +133,7 @@ class BeanOverrideBeanFactoryPostProcessor implements BeanFactoryPostProcessor,
Set<String> 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 @@ class BeanOverrideBeanFactoryPostProcessor implements BeanFactoryPostProcessor,
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 @@ class BeanOverrideBeanFactoryPostProcessor implements BeanFactoryPostProcessor,
Set<String> 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 @@ class BeanOverrideBeanFactoryPostProcessor implements BeanFactoryPostProcessor,
else {
Set<String> 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());
}
}

View File

@ -77,7 +77,7 @@ class BeanOverrideBeanFactoryPostProcessorTests {
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");
}

View File

@ -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.
*
* <p><strong>This suite is only intended to be used manually within an IDE.</strong>
*
* <h3>Logging Configuration</h3>
*
* <p>In order for our log4j2 configuration to be used in an IDE, you must
* set the following system property before running any tests &mdash; for
* example, in <em>Run Configurations</em> in Eclipse.
*
* <pre style="code">
* -Djava.util.logging.manager=org.apache.logging.log4j.jul.LogManager
* </pre>
*
* @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 {
}

View File

@ -21,28 +21,28 @@ import org.springframework.context.annotation.Configuration;
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 @@ class AbstractTestBeanIntegrationTestCase {
this.value = value;
}
@Override
public String getValue() {
return this.value;
}
@ -94,4 +95,5 @@ class AbstractTestBeanIntegrationTestCase {
return new ProdPojo();
}
}
}

View File

@ -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");
}
}
}
}

View File

@ -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() {
}
}
}

View File

@ -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 @@ public class FailingTestBeanIntegrationTests {
}
@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");
}

View File

@ -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.bean.override.example.RealExampleService
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.fail;
import static org.assertj.core.api.InstanceOfAssertFactories.THROWABLE;
import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass;
@SpringJUnitConfig(TestBeanByTypeIntegrationTests.ConfigByType.class)
/**
* {@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 @@ public class TestBeanByTypeIntegrationTests {
return new StringBuilder("CustomQualifier TestBean String");
}
@Test
void overrideIsFoundByType(ApplicationContext ctx) {
assertThat(this.anyNameForService)
@ -85,42 +88,10 @@ public class TestBeanByTypeIntegrationTests {
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 @@ public class TestBeanByTypeIntegrationTests {
}
}
@SpringJUnitConfig(FailingNone.class)
static class CaseNone {
@TestBean
ExampleService example;
@Test
void test() {}
static ExampleService exampleTestOverride() {
fail("unexpected override");
return null;
}
}
@Configuration
static class FailingNone {
}
@SpringJUnitConfig(FailingTooMany.class)
static class CaseTooMany {
@TestBean
ExampleService example;
@Test
void test() {}
static ExampleService exampleTestOverride() {
fail("unexpected override");
return null;
}
}
@Configuration
static class FailingTooMany {
@Bean
ExampleService bean1() {
return new RealExampleService("1 Hello");
}
@Bean
ExampleService bean2() {
return new RealExampleService("2 Hello");
}
}
}

View File

@ -19,30 +19,33 @@ package org.springframework.test.context.bean.override.convention;
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 @@ public class TestBeanInheritanceIntegrationTests {
}
}
@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() { }
}
}

View File

@ -30,6 +30,8 @@ import static org.assertj.core.api.Assertions.assertThat;
/**
* {@link TestBean @TestBean} integration tests for success scenarios.
*
* @author Simon Baslé
* @author Sam Brannen
* @since 6.2
* @see FailingTestBeanIntegrationTests
*/
@ -82,8 +84,8 @@ public class TestBeanIntegrationTests {
@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 @@ public class TestBeanIntegrationTests {
}
@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;

View File

@ -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");
}
}
}
}

View File

@ -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");
}
}
}
}

View File

@ -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() {
}
}
}

View File

@ -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");
}
}
}

View File

@ -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 @@ public class MockitoBeanIntegrationTests {
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 @@ public class MockitoBeanIntegrationTests {
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 @@ public class MockitoBeanIntegrationTests {
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 @@ public class MockitoBeanIntegrationTests {
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 @@ public class MockitoBeanIntegrationTests {
@Configuration
static class Config {
@Bean("field")
ExampleService bean1() {
return new RealExampleService("Hello Field");
}
@Bean("nestedField")
ExampleService bean2() {
return new RealExampleService("Hello Nested Field");
}
}
}

View File

@ -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");
}
}
}

View File

@ -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");
}
}
}

View File

@ -19,9 +19,6 @@ package org.springframework.test.context.bean.override.mockito;
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.bean.override.example.ExampleService;
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 @@ public class MockitoSpyBeanIntegrationTests {
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() { }
}
}

View File

@ -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<Throwable> rootCause(Condition<Throwable>... conditions) {
List<Condition<Throwable>> list = Arrays.stream(conditions)
.map(EngineTestKitUtils::rootCause)
.collect(toList());
return Assertions.allOf(list);
}
private static Condition<Throwable> rootCause(Condition<Throwable> 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);
}
}