Merge branch '6.2.x'

This commit is contained in:
Sam Brannen 2025-01-07 17:32:42 +02:00
commit e8788c7e3e
5 changed files with 59 additions and 70 deletions

View File

@ -92,8 +92,10 @@ Java::
[TIP]
====
Spring searches for the factory method to invoke in the test class, in the test class
hierarchy, and in the enclosing class hierarchy for a `@Nested` test class.
To locate the factory method to invoke, Spring searches in the class in which the
`@TestBean` field is declared, in one of its superclasses, or in any implemented
interfaces. If the `@TestBean` field is declared in a `@Nested` test class, the enclosing
class hierarchy will also be searched.
Alternatively, a factory method in an external class can be referenced via its
fully-qualified method name following the syntax `<fully-qualified class name>#<method name>`

View File

@ -44,24 +44,24 @@ import org.springframework.test.context.bean.override.BeanOverride;
* you can set the {@link #enforceOverride() enforceOverride} attribute to {@code true}
* &mdash; for example, {@code @TestBean(enforceOverride = true)}.
*
* <p>The instance is created from a zero-argument static factory method in the
* test class whose return type is compatible with the annotated field. In the
* case of a nested test class, the enclosing class hierarchy is also searched.
* Similarly, if the test class extends from a base class or implements any
* interfaces, the entire type hierarchy is searched. Alternatively, a factory
* method in an external class can be referenced via its fully-qualified method
* name following the syntax {@code <fully-qualified class name>#<method name>}
* &mdash; for example,
* <p>The instance is created from a zero-argument static factory method whose
* return type is compatible with the annotated field. The factory method can be
* declared directly in the class which declares the {@code @TestBean} field or
* within the type hierarchy above that class, including implemented interfaces.
* If the {@code @TestBean} field is declared in a nested test class, the enclosing
* class hierarchy is also searched. Alternatively, a factory method in an external
* class can be referenced via its fully-qualified method name following the syntax
* {@code <fully-qualified class name>#<method name>} &mdash; for example,
* {@code @TestBean(methodName = "org.example.TestUtils#createCustomerRepository")}.
*
* <p>The factory method is deduced as follows.
*
* <ul>
* <li>If the {@link #methodName()} is specified, look for a static method with
* that name.</li>
* <li>If a method name is not specified, look for exactly one static method
* named with either the name of the annotated field or the name of the bean
* (if specified).</li>
* <li>If the {@link #methodName methodName} is specified, Spring looks for a static
* method with that name.</li>
* <li>If a method name is not specified, Spring looks for exactly one static method
* whose name is either the name of the annotated field or the {@link #name() name}
* of the bean (if specified).</li>
* </ul>
*
* <p>Consider the following example.
@ -146,15 +146,17 @@ public @interface TestBean {
/**
* Name of the static factory method that will be used to instantiate the bean
* to override.
* <p>A search will be performed to find the factory method in the test class,
* in one of its superclasses, or in any implemented interfaces. In the case
* of a nested test class, the enclosing class hierarchy will also be searched.
* <p>A search will be performed to find the factory method in the class in
* which the {@code @TestBean} field is declared, in one of its superclasses,
* or in any implemented interfaces. If the {@code @TestBean} field is declared
* in a nested test class, the enclosing class hierarchy will also be searched.
* <p>Alternatively, a factory method in an external class can be referenced
* via its fully-qualified method name following the syntax
* {@code <fully-qualified class name>#<method name>} &mdash; for example,
* {@code @TestBean(methodName = "org.example.TestUtils#createCustomerRepository")}.
* <p>If left unspecified, the name of the factory method will be detected
* based either on the name of the annotated field or the name of the bean.
* based either on the name of the {@code @TestBean} field or the {@link #name() name}
* of the bean.
*/
String methodName() default "";

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2025 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.
@ -67,7 +67,7 @@ class TestBeanOverrideProcessor implements BeanOverrideProcessor {
Method factoryMethod;
if (!methodName.isBlank()) {
// If the user specified an explicit method name, search for that.
factoryMethod = findTestBeanFactoryMethod(testClass, field.getType(), methodName);
factoryMethod = findTestBeanFactoryMethod(field.getDeclaringClass(), field.getType(), methodName);
}
else {
// Otherwise, search for candidate factory methods whose names match either
@ -78,7 +78,7 @@ class TestBeanOverrideProcessor implements BeanOverrideProcessor {
if (beanName != null) {
candidateMethodNames.add(beanName);
}
factoryMethod = findTestBeanFactoryMethod(testClass, field.getType(), candidateMethodNames);
factoryMethod = findTestBeanFactoryMethod(field.getDeclaringClass(), field.getType(), candidateMethodNames);
}
return new TestBeanOverrideHandler(

View File

@ -48,19 +48,16 @@ public class TestBeanInheritanceIntegrationTests {
return new FakePojo("puzzle in enclosing class");
}
static Pojo enclosingClassBean() {
static Pojo enclosingClassFactoryMethod() {
return new FakePojo("in enclosing test class");
}
abstract static class AbstractTestCase {
@TestBean
Pojo someBean;
@TestBean("otherBean")
Pojo otherBean;
@TestBean("thirdBean")
@TestBean
Pojo anotherBean;
@TestBean
@ -70,8 +67,8 @@ public class TestBeanInheritanceIntegrationTests {
return new FakePojo("other in superclass");
}
static Pojo thirdBean() {
return new FakePojo("third in superclass");
static Pojo anotherBean() {
return new FakePojo("another in superclass");
}
static Pojo enigmaBean() {
@ -93,49 +90,42 @@ public class TestBeanInheritanceIntegrationTests {
@TestBean(methodName = "commonBean")
Pojo pojo;
@TestBean(name = "pojo2", methodName = "enclosingClassBean")
@TestBean(name = "pojo2", methodName = "enclosingClassFactoryMethod")
Pojo pojo2;
@TestBean(methodName = "localEnigmaBean")
@TestBean
Pojo enigmaBean;
@TestBean
Pojo puzzleBean;
// "Overrides" puzzleBean() defined in TestBeanInheritanceIntegrationTests.
static Pojo puzzleBean() {
return new FakePojo("puzzle in nested class");
}
static Pojo localEnigmaBean() {
// "Overrides" enigmaBean() defined in AbstractTestCase.
static Pojo enigmaBean() {
return new FakePojo("enigma in subclass");
}
static Pojo someBean() {
return new FakePojo("someBeanOverride");
}
// "Overrides" otherBean() defined in AbstractTestBeanIntegrationTestCase.
static Pojo otherBean() {
return new FakePojo("other in subclass");
}
@Test
void fieldInSuperclassWithFactoryMethodInSuperclass() {
assertThat(ctx.getBean("thirdBean")).as("applicationContext").hasToString("third in superclass");
assertThat(super.anotherBean.value()).as("injection point").isEqualTo("third in superclass");
assertThat(ctx.getBean("anotherBean")).as("applicationContext").hasToString("another in superclass");
assertThat(super.anotherBean.value()).as("injection point").isEqualTo("another in superclass");
}
@Test
void fieldInSuperclassWithFactoryMethodInSubclass() {
assertThat(ctx.getBean("someBean")).as("applicationContext").hasToString("someBeanOverride");
assertThat(super.someBean.value()).as("injection point").isEqualTo("someBeanOverride");
}
@Test
void fieldInSuperclassWithFactoryMethodInSupeclassAndInSubclass() {
assertThat(ctx.getBean("otherBean")).as("applicationContext").hasToString("other in subclass");
assertThat(super.otherBean.value()).as("injection point").isEqualTo("other in subclass");
@Test // gh-34204
void fieldInSuperclassWithFactoryMethodInSuperclassAndInSubclass() {
// We do not expect "other in subclass", because the @TestBean declaration in
// AbstractTestCase cannot "see" the otherBean() factory method in the subclass.
assertThat(ctx.getBean("otherBean")).as("applicationContext").hasToString("other in superclass");
assertThat(super.otherBean.value()).as("injection point").isEqualTo("other in superclass");
}
@Test
@ -150,13 +140,13 @@ public class TestBeanInheritanceIntegrationTests {
assertThat(this.pojo2.value()).as("injection point").isEqualTo("in enclosing test class");
}
@Test // gh-34194
@Test // gh-34194, gh-34204
void testBeanInSubclassOverridesTestBeanInSuperclass() {
assertThat(ctx.getBean("enigmaBean")).as("applicationContext").hasToString("enigma in subclass");
assertThat(this.enigmaBean.value()).as("injection point").isEqualTo("enigma in subclass");
}
@Test // gh-34194
@Test // gh-34194, gh-34204
void testBeanInNestedClassOverridesTestBeanInEnclosingClass() {
assertThat(ctx.getBean("puzzleBean")).as("applicationContext").hasToString("puzzle in nested class");
assertThat(this.puzzleBean.value()).as("injection point").isEqualTo("puzzle in nested class");
@ -166,18 +156,13 @@ public class TestBeanInheritanceIntegrationTests {
@Configuration(proxyBeanMethods = false)
static class Config {
@Bean
Pojo someBean() {
return new ProdPojo();
}
@Bean
Pojo otherBean() {
return new ProdPojo();
}
@Bean
Pojo thirdBean() {
Pojo anotherBean() {
return new ProdPojo();
}

View File

@ -26,9 +26,10 @@ import org.springframework.test.context.bean.override.BeanOverrideContextCustomi
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
/**
* Tests for {@link TestBean}.
* Tests for {@link TestBean @TestBean}.
*
* @author Stephane Nicoll
* @author Sam Brannen
*/
public class TestBeanTests {
@ -109,7 +110,7 @@ public class TestBeanTests {
.isThrownBy(() -> BeanOverrideContextCustomizerTestUtils.customizeApplicationContext(
FailureOverrideInParentWithoutFactoryMethod.class, context))
.withMessage("No static method found named beanToOverride() in %s with return type %s",
FailureOverrideInParentWithoutFactoryMethod.class.getName(), String.class.getName());
AbstractByNameLookup.class.getName(), String.class.getName());
}
@Test
@ -149,8 +150,7 @@ public class TestBeanTests {
@TestBean(name = "beanToOverride")
private String example;
// Expected static String example() { ... }
// or static String beanToOverride() { ... }
// No example() or beanToOverride() method
}
static class FailureMissingExplicitOverrideMethod {
@ -158,24 +158,21 @@ public class TestBeanTests {
@TestBean(methodName = "createExample")
private String example;
// Expected static String createExample() { ... }
// NO createExample() method
}
abstract static class AbstractByNameLookup {
@TestBean(methodName = "beanToOverride")
protected String beanToOverride;
}
static class FailureOverrideInParentWithoutFactoryMethod extends AbstractByNameLookup {
@TestBean
String beanToOverride;
// No beanToOverride() method
}
abstract static class AbstractCompetingMethods {
static class FailureOverrideInParentWithoutFactoryMethod extends AbstractByNameLookup {
}
@TestBean(name = "beanToOverride")
protected String example;
abstract static class AbstractCompetingMethods {
static String example() {
throw new IllegalStateException("Should not be called");
@ -184,6 +181,9 @@ public class TestBeanTests {
static class FailureCompetingOverrideMethods extends AbstractCompetingMethods {
@TestBean(name = "beanToOverride")
String example;
static String beanToOverride() {
throw new IllegalStateException("Should not be called");
}