Merge branch '6.2.x'
This commit is contained in:
commit
e8788c7e3e
|
@ -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>`
|
||||
|
|
|
@ -44,24 +44,24 @@ import org.springframework.test.context.bean.override.BeanOverride;
|
|||
* you can set the {@link #enforceOverride() enforceOverride} attribute to {@code true}
|
||||
* — 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>}
|
||||
* — 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>} — 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>} — 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 "";
|
||||
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue