Skip to content

Commit

Permalink
Revise bean override tests with a focus on AOT support
Browse files Browse the repository at this point in the history
As a follow up to the previous commit (31f8e12), 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
  • Loading branch information
sbrannen committed May 31, 2024
1 parent 31f8e12 commit 87c93d3
Show file tree
Hide file tree
Showing 19 changed files with 1,044 additions and 681 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ private void registerReplaceDefinition(ConfigurableListableBeanFactory beanFacto
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() + "')");
Expand All @@ -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());
}
}
Expand Down Expand Up @@ -183,7 +183,7 @@ private void registerWrapBean(ConfigurableListableBeanFactory beanFactory, Overr
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() + "')");
Expand All @@ -193,7 +193,7 @@ private void registerWrapBean(ConfigurableListableBeanFactory beanFactory, Overr
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());
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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");
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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 {
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand All @@ -60,6 +60,7 @@ protected FakePojo(String value) {
this.value = value;
}

@Override
public String getValue() {
return this.value;
}
Expand Down Expand Up @@ -94,4 +95,5 @@ Pojo pojo2() {
return new ProdPojo();
}
}

}
Original file line number Diff line number Diff line change
@@ -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");
}
}
}

}
Original file line number Diff line number Diff line change
@@ -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() {
}
}

}
Loading

0 comments on commit 87c93d3

Please sign in to comment.