Merge branch '6.2.x'
This commit is contained in:
commit
e8788c7e3e
|
@ -92,8 +92,10 @@ Java::
|
||||||
|
|
||||||
[TIP]
|
[TIP]
|
||||||
====
|
====
|
||||||
Spring searches for the factory method to invoke in the test class, in the test class
|
To locate the factory method to invoke, Spring searches in the class in which the
|
||||||
hierarchy, and in the enclosing class hierarchy for a `@Nested` test class.
|
`@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
|
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>`
|
fully-qualified method name following the syntax `<fully-qualified class name>#<method name>`
|
||||||
|
|
|
@ -44,24 +44,24 @@ import org.springframework.test.context.bean.override.BeanOverride;
|
||||||
* you can set the {@link #enforceOverride() enforceOverride} attribute to {@code true}
|
* you can set the {@link #enforceOverride() enforceOverride} attribute to {@code true}
|
||||||
* — for example, {@code @TestBean(enforceOverride = true)}.
|
* — for example, {@code @TestBean(enforceOverride = true)}.
|
||||||
*
|
*
|
||||||
* <p>The instance is created from a zero-argument static factory method in the
|
* <p>The instance is created from a zero-argument static factory method whose
|
||||||
* test class whose return type is compatible with the annotated field. In the
|
* return type is compatible with the annotated field. The factory method can be
|
||||||
* case of a nested test class, the enclosing class hierarchy is also searched.
|
* declared directly in the class which declares the {@code @TestBean} field or
|
||||||
* Similarly, if the test class extends from a base class or implements any
|
* within the type hierarchy above that class, including implemented interfaces.
|
||||||
* interfaces, the entire type hierarchy is searched. Alternatively, a factory
|
* If the {@code @TestBean} field is declared in a nested test class, the enclosing
|
||||||
* method in an external class can be referenced via its fully-qualified method
|
* class hierarchy is also searched. Alternatively, a factory method in an external
|
||||||
* name following the syntax {@code <fully-qualified class name>#<method name>}
|
* class can be referenced via its fully-qualified method name following the syntax
|
||||||
* — for example,
|
* {@code <fully-qualified class name>#<method name>} — for example,
|
||||||
* {@code @TestBean(methodName = "org.example.TestUtils#createCustomerRepository")}.
|
* {@code @TestBean(methodName = "org.example.TestUtils#createCustomerRepository")}.
|
||||||
*
|
*
|
||||||
* <p>The factory method is deduced as follows.
|
* <p>The factory method is deduced as follows.
|
||||||
*
|
*
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>If the {@link #methodName()} is specified, look for a static method with
|
* <li>If the {@link #methodName methodName} is specified, Spring looks for a static
|
||||||
* that name.</li>
|
* method with that name.</li>
|
||||||
* <li>If a method name is not specified, look for exactly one static method
|
* <li>If a method name is not specified, Spring looks for exactly one static method
|
||||||
* named with either the name of the annotated field or the name of the bean
|
* whose name is either the name of the annotated field or the {@link #name() name}
|
||||||
* (if specified).</li>
|
* of the bean (if specified).</li>
|
||||||
* </ul>
|
* </ul>
|
||||||
*
|
*
|
||||||
* <p>Consider the following example.
|
* <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
|
* Name of the static factory method that will be used to instantiate the bean
|
||||||
* to override.
|
* to override.
|
||||||
* <p>A search will be performed to find the factory method in the test class,
|
* <p>A search will be performed to find the factory method in the class in
|
||||||
* in one of its superclasses, or in any implemented interfaces. In the case
|
* which the {@code @TestBean} field is declared, in one of its superclasses,
|
||||||
* of a nested test class, the enclosing class hierarchy will also be searched.
|
* 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
|
* <p>Alternatively, a factory method in an external class can be referenced
|
||||||
* via its fully-qualified method name following the syntax
|
* via its fully-qualified method name following the syntax
|
||||||
* {@code <fully-qualified class name>#<method name>} — for example,
|
* {@code <fully-qualified class name>#<method name>} — for example,
|
||||||
* {@code @TestBean(methodName = "org.example.TestUtils#createCustomerRepository")}.
|
* {@code @TestBean(methodName = "org.example.TestUtils#createCustomerRepository")}.
|
||||||
* <p>If left unspecified, the name of the factory method will be detected
|
* <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 "";
|
String methodName() default "";
|
||||||
|
|
||||||
|
|
|
@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -67,7 +67,7 @@ class TestBeanOverrideProcessor implements BeanOverrideProcessor {
|
||||||
Method factoryMethod;
|
Method factoryMethod;
|
||||||
if (!methodName.isBlank()) {
|
if (!methodName.isBlank()) {
|
||||||
// If the user specified an explicit method name, search for that.
|
// 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 {
|
else {
|
||||||
// Otherwise, search for candidate factory methods whose names match either
|
// Otherwise, search for candidate factory methods whose names match either
|
||||||
|
@ -78,7 +78,7 @@ class TestBeanOverrideProcessor implements BeanOverrideProcessor {
|
||||||
if (beanName != null) {
|
if (beanName != null) {
|
||||||
candidateMethodNames.add(beanName);
|
candidateMethodNames.add(beanName);
|
||||||
}
|
}
|
||||||
factoryMethod = findTestBeanFactoryMethod(testClass, field.getType(), candidateMethodNames);
|
factoryMethod = findTestBeanFactoryMethod(field.getDeclaringClass(), field.getType(), candidateMethodNames);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new TestBeanOverrideHandler(
|
return new TestBeanOverrideHandler(
|
||||||
|
|
|
@ -48,19 +48,16 @@ public class TestBeanInheritanceIntegrationTests {
|
||||||
return new FakePojo("puzzle in enclosing class");
|
return new FakePojo("puzzle in enclosing class");
|
||||||
}
|
}
|
||||||
|
|
||||||
static Pojo enclosingClassBean() {
|
static Pojo enclosingClassFactoryMethod() {
|
||||||
return new FakePojo("in enclosing test class");
|
return new FakePojo("in enclosing test class");
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract static class AbstractTestCase {
|
abstract static class AbstractTestCase {
|
||||||
|
|
||||||
@TestBean
|
|
||||||
Pojo someBean;
|
|
||||||
|
|
||||||
@TestBean("otherBean")
|
@TestBean("otherBean")
|
||||||
Pojo otherBean;
|
Pojo otherBean;
|
||||||
|
|
||||||
@TestBean("thirdBean")
|
@TestBean
|
||||||
Pojo anotherBean;
|
Pojo anotherBean;
|
||||||
|
|
||||||
@TestBean
|
@TestBean
|
||||||
|
@ -70,8 +67,8 @@ public class TestBeanInheritanceIntegrationTests {
|
||||||
return new FakePojo("other in superclass");
|
return new FakePojo("other in superclass");
|
||||||
}
|
}
|
||||||
|
|
||||||
static Pojo thirdBean() {
|
static Pojo anotherBean() {
|
||||||
return new FakePojo("third in superclass");
|
return new FakePojo("another in superclass");
|
||||||
}
|
}
|
||||||
|
|
||||||
static Pojo enigmaBean() {
|
static Pojo enigmaBean() {
|
||||||
|
@ -93,49 +90,42 @@ public class TestBeanInheritanceIntegrationTests {
|
||||||
@TestBean(methodName = "commonBean")
|
@TestBean(methodName = "commonBean")
|
||||||
Pojo pojo;
|
Pojo pojo;
|
||||||
|
|
||||||
@TestBean(name = "pojo2", methodName = "enclosingClassBean")
|
@TestBean(name = "pojo2", methodName = "enclosingClassFactoryMethod")
|
||||||
Pojo pojo2;
|
Pojo pojo2;
|
||||||
|
|
||||||
@TestBean(methodName = "localEnigmaBean")
|
@TestBean
|
||||||
Pojo enigmaBean;
|
Pojo enigmaBean;
|
||||||
|
|
||||||
@TestBean
|
@TestBean
|
||||||
Pojo puzzleBean;
|
Pojo puzzleBean;
|
||||||
|
|
||||||
|
|
||||||
|
// "Overrides" puzzleBean() defined in TestBeanInheritanceIntegrationTests.
|
||||||
static Pojo puzzleBean() {
|
static Pojo puzzleBean() {
|
||||||
return new FakePojo("puzzle in nested class");
|
return new FakePojo("puzzle in nested class");
|
||||||
}
|
}
|
||||||
|
|
||||||
static Pojo localEnigmaBean() {
|
// "Overrides" enigmaBean() defined in AbstractTestCase.
|
||||||
|
static Pojo enigmaBean() {
|
||||||
return new FakePojo("enigma in subclass");
|
return new FakePojo("enigma in subclass");
|
||||||
}
|
}
|
||||||
|
|
||||||
static Pojo someBean() {
|
|
||||||
return new FakePojo("someBeanOverride");
|
|
||||||
}
|
|
||||||
|
|
||||||
// "Overrides" otherBean() defined in AbstractTestBeanIntegrationTestCase.
|
|
||||||
static Pojo otherBean() {
|
static Pojo otherBean() {
|
||||||
return new FakePojo("other in subclass");
|
return new FakePojo("other in subclass");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void fieldInSuperclassWithFactoryMethodInSuperclass() {
|
void fieldInSuperclassWithFactoryMethodInSuperclass() {
|
||||||
assertThat(ctx.getBean("thirdBean")).as("applicationContext").hasToString("third in superclass");
|
assertThat(ctx.getBean("anotherBean")).as("applicationContext").hasToString("another in superclass");
|
||||||
assertThat(super.anotherBean.value()).as("injection point").isEqualTo("third in superclass");
|
assertThat(super.anotherBean.value()).as("injection point").isEqualTo("another in superclass");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test // gh-34204
|
||||||
void fieldInSuperclassWithFactoryMethodInSubclass() {
|
void fieldInSuperclassWithFactoryMethodInSuperclassAndInSubclass() {
|
||||||
assertThat(ctx.getBean("someBean")).as("applicationContext").hasToString("someBeanOverride");
|
// We do not expect "other in subclass", because the @TestBean declaration in
|
||||||
assertThat(super.someBean.value()).as("injection point").isEqualTo("someBeanOverride");
|
// 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
|
|
||||||
void fieldInSuperclassWithFactoryMethodInSupeclassAndInSubclass() {
|
|
||||||
assertThat(ctx.getBean("otherBean")).as("applicationContext").hasToString("other in subclass");
|
|
||||||
assertThat(super.otherBean.value()).as("injection point").isEqualTo("other in subclass");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -150,13 +140,13 @@ public class TestBeanInheritanceIntegrationTests {
|
||||||
assertThat(this.pojo2.value()).as("injection point").isEqualTo("in enclosing test class");
|
assertThat(this.pojo2.value()).as("injection point").isEqualTo("in enclosing test class");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test // gh-34194
|
@Test // gh-34194, gh-34204
|
||||||
void testBeanInSubclassOverridesTestBeanInSuperclass() {
|
void testBeanInSubclassOverridesTestBeanInSuperclass() {
|
||||||
assertThat(ctx.getBean("enigmaBean")).as("applicationContext").hasToString("enigma in subclass");
|
assertThat(ctx.getBean("enigmaBean")).as("applicationContext").hasToString("enigma in subclass");
|
||||||
assertThat(this.enigmaBean.value()).as("injection point").isEqualTo("enigma in subclass");
|
assertThat(this.enigmaBean.value()).as("injection point").isEqualTo("enigma in subclass");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test // gh-34194
|
@Test // gh-34194, gh-34204
|
||||||
void testBeanInNestedClassOverridesTestBeanInEnclosingClass() {
|
void testBeanInNestedClassOverridesTestBeanInEnclosingClass() {
|
||||||
assertThat(ctx.getBean("puzzleBean")).as("applicationContext").hasToString("puzzle in nested class");
|
assertThat(ctx.getBean("puzzleBean")).as("applicationContext").hasToString("puzzle in nested class");
|
||||||
assertThat(this.puzzleBean.value()).as("injection point").isEqualTo("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)
|
@Configuration(proxyBeanMethods = false)
|
||||||
static class Config {
|
static class Config {
|
||||||
|
|
||||||
@Bean
|
|
||||||
Pojo someBean() {
|
|
||||||
return new ProdPojo();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
Pojo otherBean() {
|
Pojo otherBean() {
|
||||||
return new ProdPojo();
|
return new ProdPojo();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
Pojo thirdBean() {
|
Pojo anotherBean() {
|
||||||
return new ProdPojo();
|
return new ProdPojo();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -26,9 +26,10 @@ import org.springframework.test.context.bean.override.BeanOverrideContextCustomi
|
||||||
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests for {@link TestBean}.
|
* Tests for {@link TestBean @TestBean}.
|
||||||
*
|
*
|
||||||
* @author Stephane Nicoll
|
* @author Stephane Nicoll
|
||||||
|
* @author Sam Brannen
|
||||||
*/
|
*/
|
||||||
public class TestBeanTests {
|
public class TestBeanTests {
|
||||||
|
|
||||||
|
@ -109,7 +110,7 @@ public class TestBeanTests {
|
||||||
.isThrownBy(() -> BeanOverrideContextCustomizerTestUtils.customizeApplicationContext(
|
.isThrownBy(() -> BeanOverrideContextCustomizerTestUtils.customizeApplicationContext(
|
||||||
FailureOverrideInParentWithoutFactoryMethod.class, context))
|
FailureOverrideInParentWithoutFactoryMethod.class, context))
|
||||||
.withMessage("No static method found named beanToOverride() in %s with return type %s",
|
.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
|
@Test
|
||||||
|
@ -149,8 +150,7 @@ public class TestBeanTests {
|
||||||
@TestBean(name = "beanToOverride")
|
@TestBean(name = "beanToOverride")
|
||||||
private String example;
|
private String example;
|
||||||
|
|
||||||
// Expected static String example() { ... }
|
// No example() or beanToOverride() method
|
||||||
// or static String beanToOverride() { ... }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static class FailureMissingExplicitOverrideMethod {
|
static class FailureMissingExplicitOverrideMethod {
|
||||||
|
@ -158,24 +158,21 @@ public class TestBeanTests {
|
||||||
@TestBean(methodName = "createExample")
|
@TestBean(methodName = "createExample")
|
||||||
private String example;
|
private String example;
|
||||||
|
|
||||||
// Expected static String createExample() { ... }
|
// NO createExample() method
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract static class AbstractByNameLookup {
|
abstract static class AbstractByNameLookup {
|
||||||
|
|
||||||
@TestBean(methodName = "beanToOverride")
|
@TestBean
|
||||||
protected String beanToOverride;
|
String beanToOverride;
|
||||||
}
|
|
||||||
|
|
||||||
static class FailureOverrideInParentWithoutFactoryMethod extends AbstractByNameLookup {
|
|
||||||
|
|
||||||
// No beanToOverride() method
|
// No beanToOverride() method
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract static class AbstractCompetingMethods {
|
static class FailureOverrideInParentWithoutFactoryMethod extends AbstractByNameLookup {
|
||||||
|
}
|
||||||
|
|
||||||
@TestBean(name = "beanToOverride")
|
abstract static class AbstractCompetingMethods {
|
||||||
protected String example;
|
|
||||||
|
|
||||||
static String example() {
|
static String example() {
|
||||||
throw new IllegalStateException("Should not be called");
|
throw new IllegalStateException("Should not be called");
|
||||||
|
@ -184,6 +181,9 @@ public class TestBeanTests {
|
||||||
|
|
||||||
static class FailureCompetingOverrideMethods extends AbstractCompetingMethods {
|
static class FailureCompetingOverrideMethods extends AbstractCompetingMethods {
|
||||||
|
|
||||||
|
@TestBean(name = "beanToOverride")
|
||||||
|
String example;
|
||||||
|
|
||||||
static String beanToOverride() {
|
static String beanToOverride() {
|
||||||
throw new IllegalStateException("Should not be called");
|
throw new IllegalStateException("Should not be called");
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue