Remove TestOverrideMetadata and use dummy implementation
TestOverrideMetadata was doing byName lookup by default, even without a bean name. This makes writing tests that need to do by type lookup way more complicated that it should. This commit introduces DummyBean, that merely replaces two types with hardcoded values. It also exposes the raw attribute of OverrideMetadata and doesn't override anything from it to make sure the behavior is not altered. Closes gh-32982
This commit is contained in:
parent
457bf9416c
commit
45e9f04b57
|
@ -33,16 +33,10 @@ import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
|||
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||
import org.springframework.beans.factory.support.RootBeanDefinition;
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.support.SimpleThreadScope;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.test.context.MergedContextConfiguration;
|
||||
import org.springframework.test.context.bean.override.example.ExampleBeanOverrideAnnotation;
|
||||
import org.springframework.test.context.bean.override.example.ExampleService;
|
||||
import org.springframework.test.context.bean.override.example.FailingExampleService;
|
||||
import org.springframework.test.context.bean.override.example.RealExampleService;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
|
@ -56,123 +50,123 @@ import static org.mockito.Mockito.mock;
|
|||
* {@link BeanOverrideRegistrar}.
|
||||
*
|
||||
* @author Simon Baslé
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
class BeanOverrideBeanFactoryPostProcessorTests {
|
||||
|
||||
@Test
|
||||
void canReplaceExistingBeanDefinitions() {
|
||||
AnnotationConfigApplicationContext context = createContext(ReplaceBeans.class);
|
||||
context.register(ReplaceBeans.class);
|
||||
context.registerBean("explicit", ExampleService.class, () -> new RealExampleService("unexpected"));
|
||||
context.registerBean("implicitName", ExampleService.class, () -> new RealExampleService("unexpected"));
|
||||
|
||||
AnnotationConfigApplicationContext context = createContext(CaseByNameAndByType.class);
|
||||
context.registerBean("descriptionBean", String.class, () -> "Original");
|
||||
context.registerBean("someInteger", Integer.class, () -> 1);
|
||||
context.refresh();
|
||||
|
||||
assertThat(context.getBean("explicit")).isSameAs(OVERRIDE_SERVICE);
|
||||
assertThat(context.getBean("implicitName")).isSameAs(OVERRIDE_SERVICE);
|
||||
assertThat(context.getBean("descriptionBean")).isEqualTo("overridden");
|
||||
assertThat(context.getBean("someInteger")).isSameAs(42);
|
||||
}
|
||||
|
||||
@Test
|
||||
void cannotReplaceIfNoBeanMatching() {
|
||||
AnnotationConfigApplicationContext context = createContext(ReplaceBeans.class);
|
||||
context.register(ReplaceBeans.class);
|
||||
//note we don't register any original bean here
|
||||
void cannotReplaceIfNoBeanNameMatching() {
|
||||
AnnotationConfigApplicationContext context = createContext(CaseByName.class);
|
||||
|
||||
assertThatIllegalStateException()
|
||||
.isThrownBy(context::refresh)
|
||||
.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");
|
||||
.withMessage("Unable to override bean 'descriptionBean': there is no bean definition " +
|
||||
"to replace with that name of type java.lang.String");
|
||||
}
|
||||
|
||||
@Test
|
||||
void cannotReplaceIfNoBeanTypeMatching() {
|
||||
AnnotationConfigApplicationContext context = createContext(CaseByType.class);
|
||||
|
||||
assertThatIllegalStateException()
|
||||
.isThrownBy(context::refresh)
|
||||
.withMessage("Unable to override bean: no bean definitions of type java.lang.Integer " +
|
||||
"(as required by annotated field 'CaseByType.counter')");
|
||||
}
|
||||
|
||||
@Test
|
||||
void canReplaceExistingBeanDefinitionsWithCreateReplaceStrategy() {
|
||||
AnnotationConfigApplicationContext context = createContext(CreateIfOriginalIsMissingBean.class);
|
||||
context.register(CreateIfOriginalIsMissingBean.class);
|
||||
context.registerBean("explicit", ExampleService.class, () -> new RealExampleService("unexpected"));
|
||||
context.registerBean("implicitName", ExampleService.class, () -> new RealExampleService("unexpected"));
|
||||
|
||||
AnnotationConfigApplicationContext context = createContext(CaseByNameAndByTypeWithReplaceOrCreateStrategy.class);
|
||||
context.register(CaseByNameAndByTypeWithReplaceOrCreateStrategy.class);
|
||||
context.registerBean("descriptionBean", String.class, () -> "Original");
|
||||
context.registerBean("someInteger", Integer.class, () -> 1);
|
||||
context.refresh();
|
||||
|
||||
assertThat(context.getBean("explicit")).isSameAs(OVERRIDE_SERVICE);
|
||||
assertThat(context.getBean("implicitName")).isSameAs(OVERRIDE_SERVICE);
|
||||
assertThat(context.getBean("descriptionBean")).isEqualTo("overridden");
|
||||
assertThat(context.getBean("someInteger")).isEqualTo(42);
|
||||
}
|
||||
|
||||
@Test
|
||||
void canCreateIfOriginalMissingWithCreateReplaceStrategy() {
|
||||
AnnotationConfigApplicationContext context = createContext(CreateIfOriginalIsMissingBean.class);
|
||||
context.register(CreateIfOriginalIsMissingBean.class);
|
||||
//note we don't register original beans here
|
||||
|
||||
AnnotationConfigApplicationContext context = createContext(CaseByNameAndByTypeWithReplaceOrCreateStrategy.class);
|
||||
context.refresh();
|
||||
|
||||
assertThat(context.getBean("explicit")).isSameAs(OVERRIDE_SERVICE);
|
||||
assertThat(context.getBean("implicitName")).isSameAs(OVERRIDE_SERVICE);
|
||||
String byTypeGeneratedBeanName = "java.lang.Integer#0";
|
||||
assertThat(context.getBeanDefinitionNames()).contains("descriptionBean", byTypeGeneratedBeanName);
|
||||
assertThat(context.getBean("descriptionBean")).isEqualTo("overridden");
|
||||
assertThat(context.getBean(byTypeGeneratedBeanName)).isEqualTo(42);
|
||||
}
|
||||
|
||||
@Test
|
||||
void canOverrideBeanProducedByFactoryBeanWithClassObjectTypeAttribute() {
|
||||
AnnotationConfigApplicationContext context = createContext(OverriddenFactoryBean.class);
|
||||
RootBeanDefinition factoryBeanDefinition = new RootBeanDefinition(TestFactoryBean.class);
|
||||
factoryBeanDefinition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE, SomeInterface.class);
|
||||
context.registerBeanDefinition("beanToBeOverridden", factoryBeanDefinition);
|
||||
context.register(OverriddenFactoryBean.class);
|
||||
|
||||
AnnotationConfigApplicationContext context = prepareContextWithFactoryBean(CharSequence.class);
|
||||
context.refresh();
|
||||
|
||||
assertThat(context.getBean("beanToBeOverridden")).isSameAs(OVERRIDE);
|
||||
assertThat(context.getBean("beanToBeOverridden")).isEqualTo("overridden");
|
||||
}
|
||||
|
||||
@Test
|
||||
void canOverrideBeanProducedByFactoryBeanWithResolvableTypeObjectTypeAttribute() {
|
||||
AnnotationConfigApplicationContext context = createContext(OverriddenFactoryBean.class);
|
||||
RootBeanDefinition factoryBeanDefinition = new RootBeanDefinition(TestFactoryBean.class);
|
||||
ResolvableType objectType = ResolvableType.forClass(SomeInterface.class);
|
||||
factoryBeanDefinition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE, objectType);
|
||||
context.registerBeanDefinition("beanToBeOverridden", factoryBeanDefinition);
|
||||
context.register(OverriddenFactoryBean.class);
|
||||
|
||||
AnnotationConfigApplicationContext context = prepareContextWithFactoryBean(ResolvableType.forClass(CharSequence.class));
|
||||
context.refresh();
|
||||
|
||||
assertThat(context.getBean("beanToBeOverridden")).isSameAs(OVERRIDE);
|
||||
assertThat(context.getBean("beanToBeOverridden")).isEqualTo("overridden");
|
||||
}
|
||||
|
||||
private AnnotationConfigApplicationContext prepareContextWithFactoryBean(Object objectTypeAttribute) {
|
||||
AnnotationConfigApplicationContext context = createContext(CaseOverrideBeanProducedByFactoryBean.class);
|
||||
context.registerBean("testFactoryBean", TestFactoryBean.class, TestFactoryBean::new);
|
||||
RootBeanDefinition factoryBeanDefinition = new RootBeanDefinition(TestFactoryBean.class);
|
||||
factoryBeanDefinition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE, objectTypeAttribute);
|
||||
context.registerBeanDefinition("beanToBeOverridden", factoryBeanDefinition);
|
||||
return context;
|
||||
}
|
||||
|
||||
@Test
|
||||
void postProcessorShouldNotTriggerEarlyInitialization() {
|
||||
AnnotationConfigApplicationContext context = createContext(EagerInitBean.class);
|
||||
AnnotationConfigApplicationContext context = createContext(CaseByTypeWithReplaceOrCreateStrategy.class);
|
||||
|
||||
context.register(FactoryBeanRegisteringPostProcessor.class);
|
||||
context.register(EarlyBeanInitializationDetector.class);
|
||||
context.register(EagerInitBean.class);
|
||||
|
||||
assertThatNoException().isThrownBy(context::refresh);
|
||||
}
|
||||
|
||||
@Test
|
||||
void allowReplaceDefinitionWhenSingletonDefinitionPresent() {
|
||||
AnnotationConfigApplicationContext context = createContext(SingletonBean.class);
|
||||
AnnotationConfigApplicationContext context = createContext(CaseByName.class);
|
||||
RootBeanDefinition definition = new RootBeanDefinition(String.class, () -> "ORIGINAL");
|
||||
definition.setScope(BeanDefinition.SCOPE_SINGLETON);
|
||||
context.registerBeanDefinition("singleton", definition);
|
||||
context.register(SingletonBean.class);
|
||||
context.registerBeanDefinition("descriptionBean", definition);
|
||||
|
||||
assertThatNoException().isThrownBy(context::refresh);
|
||||
assertThat(context.isSingleton("singleton")).as("isSingleton").isTrue();
|
||||
assertThat(context.getBean("singleton")).as("overridden").isEqualTo("USED THIS");
|
||||
assertThat(context.isSingleton("descriptionBean")).as("isSingleton").isTrue();
|
||||
assertThat(context.getBean("descriptionBean")).isEqualTo("overridden");
|
||||
}
|
||||
|
||||
@Test
|
||||
void copyDefinitionPrimaryFallbackAndScope() {
|
||||
AnnotationConfigApplicationContext context = createContext(SingletonBean.class);
|
||||
AnnotationConfigApplicationContext context = createContext(CaseByName.class);
|
||||
context.getBeanFactory().registerScope("customScope", new SimpleThreadScope());
|
||||
RootBeanDefinition definition = new RootBeanDefinition(String.class, () -> "ORIGINAL");
|
||||
definition.setScope("customScope");
|
||||
definition.setPrimary(true);
|
||||
definition.setFallback(true);
|
||||
context.registerBeanDefinition("singleton", definition);
|
||||
context.register(SingletonBean.class);
|
||||
context.registerBeanDefinition("descriptionBean", definition);
|
||||
|
||||
assertThatNoException().isThrownBy(context::refresh);
|
||||
assertThat(context.getBeanDefinition("singleton"))
|
||||
assertThat(context.getBeanDefinition("descriptionBean"))
|
||||
.isNotSameAs(definition)
|
||||
.matches(BeanDefinition::isPrimary, "isPrimary")
|
||||
.matches(BeanDefinition::isFallback, "isFallback")
|
||||
|
@ -183,22 +177,20 @@ class BeanOverrideBeanFactoryPostProcessorTests {
|
|||
|
||||
@Test
|
||||
void createDefinitionShouldSetQualifierElement() {
|
||||
AnnotationConfigApplicationContext context = createContext(QualifiedBean.class);
|
||||
context.registerBeanDefinition("singleton", new RootBeanDefinition(String.class, () -> "ORIGINAL"));
|
||||
context.register(QualifiedBean.class);
|
||||
AnnotationConfigApplicationContext context = createContext(CaseByNameWithQualifier.class);
|
||||
context.registerBeanDefinition("descriptionBean", new RootBeanDefinition(String.class, () -> "ORIGINAL"));
|
||||
|
||||
assertThatNoException().isThrownBy(context::refresh);
|
||||
|
||||
assertThat(context.getBeanDefinition("singleton"))
|
||||
assertThat(context.getBeanDefinition("descriptionBean"))
|
||||
.isInstanceOfSatisfying(RootBeanDefinition.class, this::isTheValueField);
|
||||
}
|
||||
|
||||
|
||||
private void isTheValueField(RootBeanDefinition def) {
|
||||
assertThat(def.getQualifiedElement()).isInstanceOfSatisfying(Field.class, field -> {
|
||||
assertThat(field.getDeclaringClass()).isEqualTo(QualifiedBean.class);
|
||||
assertThat(field.getDeclaringClass()).isEqualTo(CaseByNameWithQualifier.class);
|
||||
assertThat(field.getName()).as("annotated field name")
|
||||
.isEqualTo("value");
|
||||
.isEqualTo("description");
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -210,99 +202,67 @@ class BeanOverrideBeanFactoryPostProcessorTests {
|
|||
}
|
||||
|
||||
|
||||
/*
|
||||
Classes to parse and register with the bean post processor
|
||||
-----
|
||||
Note that some of these are both a @Configuration class and bean override field holder.
|
||||
This is for this test convenience, as typically the bean override annotated fields
|
||||
should not be in configuration classes but rather in test case classes
|
||||
(where a TestExecutionListener automatically discovers and parses them).
|
||||
*/
|
||||
static class CaseByName {
|
||||
|
||||
static final SomeInterface OVERRIDE = new SomeImplementation();
|
||||
@DummyBean(beanName = "descriptionBean")
|
||||
private String description;
|
||||
|
||||
static final ExampleService OVERRIDE_SERVICE = new FailingExampleService();
|
||||
|
||||
static class ReplaceBeans {
|
||||
|
||||
@ExampleBeanOverrideAnnotation(value = "useThis", beanName = "explicit")
|
||||
private ExampleService explicitName;
|
||||
|
||||
@ExampleBeanOverrideAnnotation(value = "useThis")
|
||||
private ExampleService implicitName;
|
||||
|
||||
static ExampleService useThis() {
|
||||
return OVERRIDE_SERVICE;
|
||||
}
|
||||
}
|
||||
|
||||
static class CreateIfOriginalIsMissingBean {
|
||||
static class CaseByType {
|
||||
|
||||
@ExampleBeanOverrideAnnotation(value = "useThis", createIfMissing = true, beanName = "explicit")
|
||||
private ExampleService explicitName;
|
||||
@DummyBean
|
||||
private Integer counter;
|
||||
|
||||
@ExampleBeanOverrideAnnotation(value = "useThis", createIfMissing = true)
|
||||
private ExampleService implicitName;
|
||||
|
||||
static ExampleService useThis() {
|
||||
return OVERRIDE_SERVICE;
|
||||
}
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class OverriddenFactoryBean {
|
||||
static class CaseByNameAndByType {
|
||||
|
||||
@ExampleBeanOverrideAnnotation(value = "fOverride", beanName = "beanToBeOverridden")
|
||||
SomeInterface f;
|
||||
@DummyBean(beanName = "descriptionBean")
|
||||
private String description;
|
||||
|
||||
static SomeInterface fOverride() {
|
||||
return OVERRIDE;
|
||||
}
|
||||
@DummyBean
|
||||
private Integer counter;
|
||||
|
||||
@Bean
|
||||
TestFactoryBean testFactoryBean() {
|
||||
return new TestFactoryBean();
|
||||
}
|
||||
}
|
||||
|
||||
static class EagerInitBean {
|
||||
static class CaseByTypeWithReplaceOrCreateStrategy {
|
||||
|
||||
@ExampleBeanOverrideAnnotation(value = "useThis", createIfMissing = true)
|
||||
private ExampleService service;
|
||||
@DummyBean(strategy = BeanOverrideStrategy.REPLACE_OR_CREATE_DEFINITION)
|
||||
private String description;
|
||||
|
||||
static ExampleService useThis() {
|
||||
return OVERRIDE_SERVICE;
|
||||
}
|
||||
}
|
||||
|
||||
static class SingletonBean {
|
||||
static class CaseByNameAndByTypeWithReplaceOrCreateStrategy {
|
||||
|
||||
@ExampleBeanOverrideAnnotation(beanName = "singleton",
|
||||
value = "useThis", createIfMissing = false)
|
||||
private String value;
|
||||
@DummyBean(beanName = "descriptionBean", strategy = BeanOverrideStrategy.REPLACE_OR_CREATE_DEFINITION)
|
||||
private String description;
|
||||
|
||||
@DummyBean(strategy = BeanOverrideStrategy.REPLACE_OR_CREATE_DEFINITION)
|
||||
private Integer counter;
|
||||
|
||||
static String useThis() {
|
||||
return "USED THIS";
|
||||
}
|
||||
}
|
||||
|
||||
static class QualifiedBean {
|
||||
static class CaseOverrideBeanProducedByFactoryBean {
|
||||
|
||||
@DummyBean(beanName = "beanToBeOverridden")
|
||||
CharSequence description;
|
||||
|
||||
}
|
||||
|
||||
static class CaseByNameWithQualifier {
|
||||
|
||||
@Qualifier("preferThis")
|
||||
@ExampleBeanOverrideAnnotation(beanName = "singleton",
|
||||
value = "useThis", createIfMissing = false)
|
||||
private String value;
|
||||
@DummyBean(beanName = "descriptionBean")
|
||||
private String description;
|
||||
|
||||
static String useThis() {
|
||||
return "USED THIS";
|
||||
}
|
||||
}
|
||||
|
||||
static class TestFactoryBean implements FactoryBean<Object> {
|
||||
|
||||
@Override
|
||||
public Object getObject() {
|
||||
return new SomeImplementation();
|
||||
return "test";
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -341,10 +301,4 @@ class BeanOverrideBeanFactoryPostProcessorTests {
|
|||
}
|
||||
}
|
||||
|
||||
interface SomeInterface {
|
||||
}
|
||||
|
||||
static class SomeImplementation implements SomeInterface {
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -25,10 +25,9 @@ import org.junit.jupiter.api.Test;
|
|||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.test.context.bean.override.BeanOverrideContextCustomizerFactoryTests.Test2.Green;
|
||||
import org.springframework.test.context.bean.override.BeanOverrideContextCustomizerFactoryTests.Test2.Orange;
|
||||
import org.springframework.test.context.bean.override.convention.TestBean;
|
||||
import org.springframework.test.context.bean.override.DummyBean.DummyBeanOverrideProcessor.DummyOverrideMetadata;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
||||
|
||||
/**
|
||||
* Tests for {@link BeanOverrideContextCustomizerFactory}.
|
||||
|
@ -48,14 +47,14 @@ class BeanOverrideContextCustomizerFactoryTests {
|
|||
void createContextCustomizerWhenTestHasSingleBeanOverride() {
|
||||
BeanOverrideContextCustomizer customizer = createContextCustomizer(Test1.class);
|
||||
assertThat(customizer).isNotNull();
|
||||
assertThat(customizer.getMetadata()).singleElement().satisfies(testMetadata(null, String.class));
|
||||
assertThat(customizer.getMetadata()).singleElement().satisfies(dummyMetadata(null, String.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void createContextCustomizerWhenNestedTestHasSingleBeanOverrideInParent() {
|
||||
BeanOverrideContextCustomizer customizer = createContextCustomizer(Orange.class);
|
||||
assertThat(customizer).isNotNull();
|
||||
assertThat(customizer.getMetadata()).singleElement().satisfies(testMetadata(null, String.class));
|
||||
assertThat(customizer.getMetadata()).singleElement().satisfies(dummyMetadata(null, String.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -63,25 +62,19 @@ class BeanOverrideContextCustomizerFactoryTests {
|
|||
BeanOverrideContextCustomizer customizer = createContextCustomizer(Green.class);
|
||||
assertThat(customizer).isNotNull();
|
||||
assertThat(customizer.getMetadata())
|
||||
.anySatisfy(testMetadata(null, String.class))
|
||||
.anySatisfy(testMetadata("counterBean", Integer.class))
|
||||
.anySatisfy(dummyMetadata(null, String.class))
|
||||
.anySatisfy(dummyMetadata("counterBean", Integer.class))
|
||||
.hasSize(2);
|
||||
}
|
||||
|
||||
@Test
|
||||
void createContextCustomizerWhenTestHasInvalidTestBeanTargetMethod() {
|
||||
assertThatIllegalStateException()
|
||||
.isThrownBy(() -> createContextCustomizer(InvalidTestMissingMethod.class))
|
||||
.withMessageContaining("Failed to find a static test bean factory method");
|
||||
|
||||
private Consumer<OverrideMetadata> dummyMetadata(@Nullable String beanName, Class<?> beanType) {
|
||||
return dummyMetadata(beanName, beanType, BeanOverrideStrategy.REPLACE_DEFINITION);
|
||||
}
|
||||
|
||||
|
||||
private Consumer<OverrideMetadata> testMetadata(@Nullable String beanName, Class<?> beanType) {
|
||||
return overrideMetadata(beanName, beanType, BeanOverrideStrategy.REPLACE_DEFINITION);
|
||||
}
|
||||
|
||||
private Consumer<OverrideMetadata> overrideMetadata(@Nullable String beanName, Class<?> beanType, BeanOverrideStrategy strategy) {
|
||||
private Consumer<OverrideMetadata> dummyMetadata(@Nullable String beanName, Class<?> beanType, BeanOverrideStrategy strategy) {
|
||||
return metadata -> {
|
||||
assertThat(metadata).isExactlyInstanceOf(DummyOverrideMetadata.class);
|
||||
assertThat(metadata.getBeanName()).isEqualTo(beanName);
|
||||
assertThat(metadata.getBeanType().toClass()).isEqualTo(beanType);
|
||||
assertThat(metadata.getStrategy()).isEqualTo(strategy);
|
||||
|
@ -95,24 +88,16 @@ class BeanOverrideContextCustomizerFactoryTests {
|
|||
|
||||
static class Test1 {
|
||||
|
||||
@TestBean(methodName = "descriptor")
|
||||
@DummyBean
|
||||
private String descriptor;
|
||||
|
||||
private static String descriptor() {
|
||||
return "Overridden descriptor";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class Test2 {
|
||||
|
||||
@TestBean(methodName = "name")
|
||||
@DummyBean
|
||||
private String name;
|
||||
|
||||
private static String name() {
|
||||
return "Overridden name";
|
||||
}
|
||||
|
||||
@Nested
|
||||
class Orange {
|
||||
|
||||
|
@ -121,24 +106,10 @@ class BeanOverrideContextCustomizerFactoryTests {
|
|||
@Nested
|
||||
class Green {
|
||||
|
||||
@TestBean(name = "counterBean", methodName = "counter")
|
||||
@DummyBean(beanName = "counterBean")
|
||||
private Integer counter;
|
||||
|
||||
private static Integer counter() {
|
||||
return 42;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static class InvalidTestMissingMethod {
|
||||
|
||||
@TestBean(methodName = "descriptor")
|
||||
private String descriptor;
|
||||
|
||||
private String descriptor() {
|
||||
return "Never called, not static";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,86 @@
|
|||
/*
|
||||
* 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 java.lang.annotation.Annotation;
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.test.context.bean.override.DummyBean.DummyBeanOverrideProcessor;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* A dummy {@link BeanOverride} implementation that only handles {@link CharSequence}
|
||||
* and {@link Integer} and replace them with {@code "overridden"} and {@code 42},
|
||||
* respectively.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
@Target({ ElementType.FIELD, ElementType.ANNOTATION_TYPE })
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
@BeanOverride(DummyBeanOverrideProcessor.class)
|
||||
@interface DummyBean {
|
||||
|
||||
String beanName() default "";
|
||||
|
||||
BeanOverrideStrategy strategy() default BeanOverrideStrategy.REPLACE_DEFINITION;
|
||||
|
||||
class DummyBeanOverrideProcessor implements BeanOverrideProcessor {
|
||||
|
||||
@Override
|
||||
public OverrideMetadata createMetadata(Annotation annotation, Class<?> testClass, Field field) {
|
||||
DummyBean dummyBean = (DummyBean) annotation;
|
||||
String beanName = (StringUtils.hasText(dummyBean.beanName()) ? dummyBean.beanName() : null);
|
||||
return new DummyBeanOverrideProcessor.DummyOverrideMetadata(field, field.getType(), beanName,
|
||||
dummyBean.strategy());
|
||||
}
|
||||
|
||||
// Bare bone, "dummy", implementation that should not override anything
|
||||
// else than createOverride.
|
||||
static class DummyOverrideMetadata extends OverrideMetadata {
|
||||
|
||||
DummyOverrideMetadata(Field field, Class<?> typeToOverride, @Nullable String beanName,
|
||||
BeanOverrideStrategy strategy) {
|
||||
|
||||
super(field, ResolvableType.forClass(typeToOverride), beanName, strategy);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object createOverride(String beanName, @Nullable BeanDefinition existingBeanDefinition,
|
||||
@Nullable Object existingBeanInstance) {
|
||||
|
||||
Class<?> beanType = getField().getType();
|
||||
if (CharSequence.class.isAssignableFrom(beanType)) {
|
||||
return "overridden";
|
||||
}
|
||||
else if (Integer.class.isAssignableFrom(beanType)) {
|
||||
return 42;
|
||||
}
|
||||
throw new IllegalStateException("Could not handle bean type " + beanType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -16,8 +16,6 @@
|
|||
|
||||
package org.springframework.test.context.bean.override;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
@ -30,9 +28,8 @@ import java.util.function.Consumer;
|
|||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.test.context.bean.override.DummyBean.DummyBeanOverrideProcessor.DummyOverrideMetadata;
|
||||
import org.springframework.test.context.bean.override.example.CustomQualifier;
|
||||
import org.springframework.test.context.bean.override.example.ExampleService;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
@ -157,7 +154,7 @@ public class OverrideMetadataTests {
|
|||
}
|
||||
|
||||
private OverrideMetadata createMetadata(Field field, @Nullable String name) {
|
||||
return new DummyOverrideMetadata(field, field.getType(), name);
|
||||
return new DummyOverrideMetadata(field, field.getType(), name, BeanOverrideStrategy.REPLACE_DEFINITION);
|
||||
}
|
||||
|
||||
private Field field(Class<?> target, String fieldName) {
|
||||
|
@ -248,42 +245,4 @@ public class OverrideMetadataTests {
|
|||
@DummyBean
|
||||
public @interface MetaDummyBean {}
|
||||
|
||||
@Target({ ElementType.FIELD, ElementType.ANNOTATION_TYPE })
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
@BeanOverride(DummyBeanOverrideProcessor.class)
|
||||
public @interface DummyBean {}
|
||||
|
||||
static class DummyBeanOverrideProcessor implements BeanOverrideProcessor {
|
||||
|
||||
@Override
|
||||
public OverrideMetadata createMetadata(Annotation overrideAnnotation, Class<?> testClass, Field field) {
|
||||
return new DummyOverrideMetadata(field, field.getType(), null);
|
||||
}
|
||||
}
|
||||
|
||||
static class DummyOverrideMetadata extends OverrideMetadata {
|
||||
|
||||
@Nullable
|
||||
private final String beanName;
|
||||
|
||||
DummyOverrideMetadata(Field field, Class<?> typeToOverride, @Nullable String beanName) {
|
||||
super(field, ResolvableType.forClass(typeToOverride), beanName, BeanOverrideStrategy.REPLACE_DEFINITION);
|
||||
this.beanName = beanName;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public String getBeanName() {
|
||||
return this.beanName;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object createOverride(String beanName, @Nullable BeanDefinition existingBeanDefinition,
|
||||
@Nullable Object existingBeanInstance) {
|
||||
|
||||
return BeanOverrideStrategy.REPLACE_DEFINITION;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,40 +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.example;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.springframework.test.context.bean.override.BeanOverride;
|
||||
|
||||
@BeanOverride(ExampleBeanOverrideProcessor.class)
|
||||
@Target({ElementType.FIELD, ElementType.ANNOTATION_TYPE})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface ExampleBeanOverrideAnnotation {
|
||||
|
||||
String DEFAULT_VALUE = "TEST OVERRIDE";
|
||||
// Any metadata using this as the prefix for the bean name will be considered equal
|
||||
String DUPLICATE_TRIGGER = "DUPLICATE";
|
||||
|
||||
String value() default DEFAULT_VALUE;
|
||||
|
||||
boolean createIfMissing() default false;
|
||||
|
||||
String beanName() default "";
|
||||
}
|
|
@ -1,40 +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.example;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.test.context.bean.override.BeanOverrideProcessor;
|
||||
import org.springframework.test.context.bean.override.OverrideMetadata;
|
||||
|
||||
// Intentionally NOT public
|
||||
class ExampleBeanOverrideProcessor implements BeanOverrideProcessor {
|
||||
|
||||
@Override
|
||||
public OverrideMetadata createMetadata(Annotation overrideAnnotation, Class<?> testClass, Field field) {
|
||||
if (!(overrideAnnotation instanceof ExampleBeanOverrideAnnotation annotation)) {
|
||||
throw new IllegalStateException("unexpected annotation");
|
||||
}
|
||||
if (annotation.value().startsWith(ExampleBeanOverrideAnnotation.DUPLICATE_TRIGGER)) {
|
||||
return new TestOverrideMetadata(annotation.value());
|
||||
}
|
||||
return new TestOverrideMetadata(field, annotation, ResolvableType.forField(field, testClass));
|
||||
}
|
||||
|
||||
}
|
|
@ -1,27 +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.example;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Target({ElementType.FIELD, ElementType.TYPE})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@ExampleBeanOverrideAnnotation("foo")
|
||||
public @interface TestBeanOverrideMetaAnnotation { }
|
|
@ -1,144 +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.example;
|
||||
|
||||
|
||||
import java.lang.reflect.AnnotatedElement;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.test.context.bean.override.BeanOverrideStrategy;
|
||||
import org.springframework.test.context.bean.override.OverrideMetadata;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import static org.springframework.test.context.bean.override.example.ExampleBeanOverrideAnnotation.DEFAULT_VALUE;
|
||||
|
||||
class TestOverrideMetadata extends OverrideMetadata {
|
||||
|
||||
@Nullable
|
||||
private final Method method;
|
||||
|
||||
private final String methodName;
|
||||
|
||||
@Nullable
|
||||
private static Method findMethod(AnnotatedElement element, String methodName) {
|
||||
if (DEFAULT_VALUE.equals(methodName)) {
|
||||
return null;
|
||||
}
|
||||
if (element instanceof Field f) {
|
||||
for (Method m : f.getDeclaringClass().getDeclaredMethods()) {
|
||||
if (!Modifier.isStatic(m.getModifiers())) {
|
||||
continue;
|
||||
}
|
||||
if (m.getName().equals(methodName)) {
|
||||
return m;
|
||||
}
|
||||
}
|
||||
throw new IllegalStateException("Expected a static method named <" + methodName + "> alongside annotated field <" + f.getName() + ">");
|
||||
}
|
||||
if (element instanceof Method m) {
|
||||
if (m.getName().equals(methodName) && Modifier.isStatic(m.getModifiers())) {
|
||||
return m;
|
||||
}
|
||||
throw new IllegalStateException("Expected the annotated method to be static and named <" + methodName + ">");
|
||||
}
|
||||
if (element instanceof Class c) {
|
||||
for (Method m : c.getDeclaredMethods()) {
|
||||
if (!Modifier.isStatic(m.getModifiers())) {
|
||||
continue;
|
||||
}
|
||||
if (m.getName().equals(methodName)) {
|
||||
return m;
|
||||
}
|
||||
}
|
||||
throw new IllegalStateException("Expected a static method named <" + methodName + "> on annotated class <" + c.getSimpleName() + ">");
|
||||
}
|
||||
throw new IllegalStateException("Expected the annotated element to be a Field, Method or Class");
|
||||
}
|
||||
|
||||
public TestOverrideMetadata(Field field, ExampleBeanOverrideAnnotation overrideAnnotation, ResolvableType typeToOverride) {
|
||||
super(field, typeToOverride, overrideAnnotation.beanName(), overrideAnnotation.createIfMissing() ?
|
||||
BeanOverrideStrategy.REPLACE_OR_CREATE_DEFINITION: BeanOverrideStrategy.REPLACE_DEFINITION);
|
||||
this.method = findMethod(field, overrideAnnotation.value());
|
||||
this.methodName = overrideAnnotation.value();
|
||||
}
|
||||
|
||||
//Used to trigger duplicate detection in parser test
|
||||
TestOverrideMetadata(String duplicateTrigger) {
|
||||
super(null, null, duplicateTrigger, null);
|
||||
this.method = null;
|
||||
this.methodName = duplicateTrigger;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getBeanName() {
|
||||
String name = super.getBeanName();
|
||||
if (StringUtils.hasText(name)) {
|
||||
return name;
|
||||
}
|
||||
return getField().getName();
|
||||
}
|
||||
|
||||
String getAnnotationMethodName() {
|
||||
return this.methodName;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object createOverride(String beanName, @Nullable BeanDefinition existingBeanDefinition, @Nullable Object existingBeanInstance) {
|
||||
if (this.method == null) {
|
||||
return DEFAULT_VALUE;
|
||||
}
|
||||
try {
|
||||
this.method.setAccessible(true);
|
||||
return this.method.invoke(null);
|
||||
}
|
||||
catch (IllegalAccessException | InvocationTargetException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this.method == null) {
|
||||
return obj instanceof TestOverrideMetadata tem &&
|
||||
tem.getBeanName() != null &&
|
||||
tem.getBeanName().startsWith(ExampleBeanOverrideAnnotation.DUPLICATE_TRIGGER);
|
||||
}
|
||||
return super.equals(obj);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
if (this.method == null) {
|
||||
return ExampleBeanOverrideAnnotation.DUPLICATE_TRIGGER.hashCode();
|
||||
}
|
||||
return super.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (this.method == null) {
|
||||
return "{" + this.getBeanName() + "}";
|
||||
}
|
||||
return this.methodName;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue