Browse Source
This commit reviews how bean override support can influence the key of an application context cached by the TCF. OverrideMetadata and its subclasses now implement a proper equals/hashCode pair that is tested in various scenarios. Due to how the TCF operates, OverrideMetadata has to be computed in two locations: 1. In a ContextCustomizerFactory, using the metadata in the enclosing class if any. This determines whether a customizer is needed in the first place. The computed set of unique metadata identifies the customizer and participates in the application context cache's key. 2. In the TestExecutionListener so that it knows the override points it has to process. Parsing of the metadata based on a test class has been greatly simplified and moved to OverrideMetadata proper as we don't need several flavors. 1 and 2 are using the same algorithm with the former wrapping that in a Set to compute a proper key. BeanOverrideContextCustomizerEqualityTests provides a framework for testing edge cases as we only care about whether the created ContextCustomizer behaves correctly against the identity of another. Closes gh-32884pull/32992/head
23 changed files with 1239 additions and 331 deletions
@ -1,101 +0,0 @@
@@ -1,101 +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; |
||||
|
||||
import java.lang.annotation.Annotation; |
||||
import java.lang.reflect.Field; |
||||
import java.util.LinkedHashSet; |
||||
import java.util.List; |
||||
import java.util.Set; |
||||
import java.util.concurrent.atomic.AtomicBoolean; |
||||
|
||||
import org.springframework.beans.BeanUtils; |
||||
import org.springframework.core.annotation.MergedAnnotation; |
||||
import org.springframework.core.annotation.MergedAnnotations; |
||||
import org.springframework.util.Assert; |
||||
import org.springframework.util.ReflectionUtils; |
||||
|
||||
import static org.springframework.core.annotation.MergedAnnotations.SearchStrategy.DIRECT; |
||||
|
||||
/** |
||||
* Internal parsing utilities to discover the presence of |
||||
* {@link BeanOverride @BeanOverride} and create the relevant |
||||
* {@link OverrideMetadata} if necessary. |
||||
* |
||||
* @author Simon Baslé |
||||
* @author Sam Brannen |
||||
* @since 6.2 |
||||
*/ |
||||
abstract class BeanOverrideParsingUtils { |
||||
|
||||
/** |
||||
* Check if at least one field of the given {@code clazz} is meta-annotated |
||||
* with {@link BeanOverride @BeanOverride}. |
||||
* @param clazz the class which fields to inspect |
||||
* @return {@code true} if there is a bean override annotation present, |
||||
* {@code false} otherwise |
||||
*/ |
||||
static boolean hasBeanOverride(Class<?> clazz) { |
||||
AtomicBoolean hasBeanOverride = new AtomicBoolean(); |
||||
ReflectionUtils.doWithFields(clazz, field -> { |
||||
if (hasBeanOverride.get()) { |
||||
return; |
||||
} |
||||
boolean present = MergedAnnotations.from(field, DIRECT).isPresent(BeanOverride.class); |
||||
hasBeanOverride.compareAndSet(false, present); |
||||
}); |
||||
return hasBeanOverride.get(); |
||||
} |
||||
|
||||
/** |
||||
* Parse the specified classes for the presence of fields annotated with |
||||
* {@link BeanOverride @BeanOverride}, and create an {@link OverrideMetadata} |
||||
* for each identified field. |
||||
* @param classes the classes to parse |
||||
*/ |
||||
static Set<OverrideMetadata> parse(Iterable<Class<?>> classes) { |
||||
Set<OverrideMetadata> result = new LinkedHashSet<>(); |
||||
classes.forEach(c -> ReflectionUtils.doWithFields(c, field -> parseField(field, c, result))); |
||||
return result; |
||||
} |
||||
|
||||
/** |
||||
* Convenience method to parse a single test class. |
||||
* @see #parse(Iterable) |
||||
*/ |
||||
static Set<OverrideMetadata> parse(Class<?> clazz) { |
||||
return parse(List.of(clazz)); |
||||
} |
||||
|
||||
private static void parseField(Field field, Class<?> testClass, Set<OverrideMetadata> metadataSet) { |
||||
AtomicBoolean overrideAnnotationFound = new AtomicBoolean(); |
||||
MergedAnnotations.from(field, DIRECT).stream(BeanOverride.class).forEach(mergedAnnotation -> { |
||||
MergedAnnotation<?> metaSource = mergedAnnotation.getMetaSource(); |
||||
Assert.state(metaSource != null, "@BeanOverride annotation must be meta-present"); |
||||
|
||||
BeanOverride beanOverride = mergedAnnotation.synthesize(); |
||||
BeanOverrideProcessor processor = BeanUtils.instantiateClass(beanOverride.value()); |
||||
Annotation composedAnnotation = metaSource.synthesize(); |
||||
|
||||
Assert.state(overrideAnnotationFound.compareAndSet(false, true), |
||||
() -> "Multiple @BeanOverride annotations found on field: " + field); |
||||
OverrideMetadata metadata = processor.createMetadata(composedAnnotation, testClass, field); |
||||
metadataSet.add(metadata); |
||||
}); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,168 @@
@@ -0,0 +1,168 @@
|
||||
/* |
||||
* 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.util.Collections; |
||||
|
||||
import org.junit.jupiter.api.Nested; |
||||
import org.junit.jupiter.api.Test; |
||||
import org.mockito.Answers; |
||||
|
||||
import org.springframework.test.context.ContextCustomizer; |
||||
import org.springframework.test.context.bean.override.convention.TestBean; |
||||
import org.springframework.test.context.bean.override.mockito.MockitoBean; |
||||
import org.springframework.test.context.bean.override.mockito.MockitoSpyBean; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
|
||||
/** |
||||
* Bean override tests that validate the behavior with the TCF context cache. |
||||
* |
||||
* @author Stephane Nicoll |
||||
*/ |
||||
@SuppressWarnings("unused") |
||||
class BeanOverrideContextCustomizerEqualityTests { |
||||
|
||||
private final BeanOverrideContextCustomizerFactory factory = new BeanOverrideContextCustomizerFactory(); |
||||
|
||||
@Nested |
||||
class SameContextTests { |
||||
|
||||
@Test |
||||
void testsWithOneIdenticalTestBean() { |
||||
assertThat(createContextCustomizer(Case1.class)).isEqualTo(createContextCustomizer(Case2.class)); |
||||
} |
||||
|
||||
@Test |
||||
void testsWithOneIdenticalMockitoMockBean() { |
||||
assertThat(createContextCustomizer(Case4.class)).isEqualTo(createContextCustomizer(Case5.class)); |
||||
} |
||||
|
||||
@Test |
||||
void testsWithOneIdenticalMockitoSpyBean() { |
||||
assertThat(createContextCustomizer(Case7.class)).isEqualTo(createContextCustomizer(Case8.class)); |
||||
} |
||||
} |
||||
|
||||
@Nested |
||||
class DifferentContextTests { |
||||
|
||||
@Test |
||||
void testsWithSimilarTestBeanButDifferentMethod() { |
||||
assertThat(createContextCustomizer(Case1.class)).isNotEqualTo(createContextCustomizer(Case3.class)); |
||||
} |
||||
|
||||
@Test |
||||
void testsWithSimilarMockitoMockButDifferentAnswers() { |
||||
assertThat(createContextCustomizer(Case4.class)).isNotEqualTo(createContextCustomizer(Case6.class)); |
||||
} |
||||
|
||||
@Test |
||||
void testsWithSimilarMockitoSpyButDifferentProxyTargetClass() { |
||||
assertThat(createContextCustomizer(Case8.class)).isNotEqualTo(createContextCustomizer(Case9.class)); |
||||
} |
||||
|
||||
@Test |
||||
void testsWithSameConfigurationButOneIsMockitoBeanAndTheOtherMockitoSpy() { |
||||
assertThat(createContextCustomizer(Case4.class)).isNotEqualTo(createContextCustomizer(Case7.class)); |
||||
} |
||||
|
||||
} |
||||
|
||||
|
||||
private ContextCustomizer createContextCustomizer(Class<?> testClass) { |
||||
BeanOverrideContextCustomizer customizer = this.factory.createContextCustomizer( |
||||
testClass, Collections.emptyList()); |
||||
assertThat(customizer).isNotNull(); |
||||
return customizer; |
||||
} |
||||
|
||||
interface DescriptionProvider { |
||||
|
||||
static String createDescription() { |
||||
return "override"; |
||||
} |
||||
|
||||
} |
||||
|
||||
static class Case1 implements DescriptionProvider { |
||||
|
||||
@TestBean(methodName = "createDescription") |
||||
private String description; |
||||
|
||||
} |
||||
|
||||
static class Case2 implements DescriptionProvider { |
||||
|
||||
@TestBean(methodName = "createDescription") |
||||
private String description; |
||||
|
||||
} |
||||
|
||||
static class Case3 implements DescriptionProvider { |
||||
|
||||
@TestBean(methodName = "createDescription") |
||||
private String description; |
||||
|
||||
static String createDescription() { |
||||
return "another value"; |
||||
} |
||||
} |
||||
|
||||
static class Case4 { |
||||
|
||||
@MockitoBean |
||||
private String exampleService; |
||||
|
||||
} |
||||
|
||||
static class Case5 { |
||||
|
||||
@MockitoBean |
||||
private String serviceToMock; |
||||
|
||||
} |
||||
|
||||
static class Case6 { |
||||
|
||||
@MockitoBean(answers = Answers.RETURNS_MOCKS) |
||||
private String exampleService; |
||||
|
||||
} |
||||
|
||||
static class Case7 { |
||||
|
||||
@MockitoSpyBean |
||||
private String exampleService; |
||||
|
||||
} |
||||
|
||||
static class Case8 { |
||||
|
||||
@MockitoSpyBean |
||||
private String serviceToMock; |
||||
|
||||
} |
||||
|
||||
static class Case9 { |
||||
|
||||
@MockitoSpyBean(proxyTargetAware = false) |
||||
private String serviceToMock; |
||||
|
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,144 @@
@@ -0,0 +1,144 @@
|
||||
/* |
||||
* 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.util.Collections; |
||||
import java.util.function.Consumer; |
||||
|
||||
import org.junit.jupiter.api.Nested; |
||||
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 static org.assertj.core.api.Assertions.assertThat; |
||||
import static org.assertj.core.api.Assertions.assertThatIllegalStateException; |
||||
|
||||
/** |
||||
* Tests for {@link BeanOverrideContextCustomizerFactory}. |
||||
* |
||||
* @author Stephane Nicoll |
||||
*/ |
||||
class BeanOverrideContextCustomizerFactoryTests { |
||||
|
||||
private final BeanOverrideContextCustomizerFactory factory = new BeanOverrideContextCustomizerFactory(); |
||||
|
||||
@Test |
||||
void createContextCustomizerWhenTestHasNoBeanOverride() { |
||||
assertThat(createContextCustomizer(String.class)).isNull(); |
||||
} |
||||
|
||||
@Test |
||||
void createContextCustomizerWhenTestHasSingleBeanOverride() { |
||||
BeanOverrideContextCustomizer customizer = createContextCustomizer(Test1.class); |
||||
assertThat(customizer).isNotNull(); |
||||
assertThat(customizer.getMetadata()).singleElement().satisfies(testMetadata(null, String.class)); |
||||
} |
||||
|
||||
@Test |
||||
void createContextCustomizerWhenNestedTestHasSingleBeanOverrideInParent() { |
||||
BeanOverrideContextCustomizer customizer = createContextCustomizer(Orange.class); |
||||
assertThat(customizer).isNotNull(); |
||||
assertThat(customizer.getMetadata()).singleElement().satisfies(testMetadata(null, String.class)); |
||||
} |
||||
|
||||
@Test |
||||
void createContextCustomizerWhenNestedTestHasBeanOverrideAsWellAsTheParent() { |
||||
BeanOverrideContextCustomizer customizer = createContextCustomizer(Green.class); |
||||
assertThat(customizer).isNotNull(); |
||||
assertThat(customizer.getMetadata()) |
||||
.anySatisfy(testMetadata(null, String.class)) |
||||
.anySatisfy(testMetadata("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> testMetadata(@Nullable String beanName, Class<?> beanType) { |
||||
return overrideMetadata(beanName, beanType, BeanOverrideStrategy.REPLACE_DEFINITION); |
||||
} |
||||
|
||||
private Consumer<OverrideMetadata> overrideMetadata(@Nullable String beanName, Class<?> beanType, BeanOverrideStrategy strategy) { |
||||
return metadata -> { |
||||
assertThat(metadata.getBeanName()).isEqualTo(beanName); |
||||
assertThat(metadata.getBeanType().toClass()).isEqualTo(beanType); |
||||
assertThat(metadata.getStrategy()).isEqualTo(strategy); |
||||
}; |
||||
} |
||||
|
||||
@Nullable |
||||
BeanOverrideContextCustomizer createContextCustomizer(Class<?> testClass) { |
||||
return this.factory.createContextCustomizer(testClass, Collections.emptyList()); |
||||
} |
||||
|
||||
static class Test1 { |
||||
|
||||
@TestBean(methodName = "descriptor") |
||||
private String descriptor; |
||||
|
||||
private static String descriptor() { |
||||
return "Overridden descriptor"; |
||||
} |
||||
|
||||
} |
||||
|
||||
static class Test2 { |
||||
|
||||
@TestBean(methodName = "name") |
||||
private String name; |
||||
|
||||
private static String name() { |
||||
return "Overridden name"; |
||||
} |
||||
|
||||
@Nested |
||||
class Orange { |
||||
|
||||
} |
||||
|
||||
@Nested |
||||
class Green { |
||||
|
||||
@TestBean(name = "counterBean", methodName = "counter") |
||||
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,103 @@
@@ -0,0 +1,103 @@
|
||||
/* |
||||
* 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.reflect.Field; |
||||
import java.util.Arrays; |
||||
import java.util.LinkedHashSet; |
||||
import java.util.Objects; |
||||
|
||||
import org.junit.jupiter.api.Test; |
||||
|
||||
import org.springframework.beans.factory.config.BeanDefinition; |
||||
import org.springframework.core.ResolvableType; |
||||
import org.springframework.lang.Nullable; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
import static org.mockito.Mockito.mock; |
||||
|
||||
/** |
||||
* Tests for {@link BeanOverrideContextCustomizer}. |
||||
* |
||||
* @author Stephane Nicoll |
||||
*/ |
||||
class BeanOverrideContextCustomizerTests { |
||||
|
||||
@Test |
||||
void customizerIsEqualWithIdenticalMetadata() { |
||||
BeanOverrideContextCustomizer customizer = createCustomizer(new DummyOverrideMetadata("key")); |
||||
BeanOverrideContextCustomizer customizer2 = createCustomizer(new DummyOverrideMetadata("key")); |
||||
assertThat(customizer).isEqualTo(customizer2); |
||||
assertThat(customizer).hasSameHashCodeAs(customizer2); |
||||
} |
||||
|
||||
@Test |
||||
void customizerIsEqualWithIdenticalMetadataInDifferentOrder() { |
||||
BeanOverrideContextCustomizer customizer = createCustomizer( |
||||
new DummyOverrideMetadata("key1"), new DummyOverrideMetadata("key2")); |
||||
BeanOverrideContextCustomizer customizer2 = createCustomizer( |
||||
new DummyOverrideMetadata("key2"), new DummyOverrideMetadata("key1")); |
||||
assertThat(customizer).isEqualTo(customizer2); |
||||
assertThat(customizer).hasSameHashCodeAs(customizer2); |
||||
} |
||||
|
||||
@Test |
||||
void customizerIsNotEqualWithDifferentMetadata() { |
||||
BeanOverrideContextCustomizer customizer = createCustomizer(new DummyOverrideMetadata("key")); |
||||
BeanOverrideContextCustomizer customizer2 = createCustomizer( |
||||
new DummyOverrideMetadata("key"), new DummyOverrideMetadata("another")); |
||||
assertThat(customizer).isNotEqualTo(customizer2); |
||||
} |
||||
|
||||
private BeanOverrideContextCustomizer createCustomizer(OverrideMetadata... metadata) { |
||||
return new BeanOverrideContextCustomizer(new LinkedHashSet<>(Arrays.asList(metadata))); |
||||
} |
||||
|
||||
private static class DummyOverrideMetadata extends OverrideMetadata { |
||||
|
||||
private final String key; |
||||
|
||||
public DummyOverrideMetadata(String key) { |
||||
super(mock(Field.class), ResolvableType.forClass(Object.class), null, BeanOverrideStrategy.REPLACE_DEFINITION); |
||||
this.key = key; |
||||
} |
||||
|
||||
@Override |
||||
protected Object createOverride(String beanName, BeanDefinition existingBeanDefinition, |
||||
Object existingBeanInstance) { |
||||
return existingBeanInstance; |
||||
} |
||||
|
||||
@Override |
||||
public boolean equals(@Nullable Object o) { |
||||
if (this == o) { |
||||
return true; |
||||
} |
||||
if (o == null || getClass() != o.getClass()) { |
||||
return false; |
||||
} |
||||
DummyOverrideMetadata that = (DummyOverrideMetadata) o; |
||||
return Objects.equals(this.key, that.key); |
||||
} |
||||
|
||||
@Override |
||||
public int hashCode() { |
||||
return this.key.hashCode(); |
||||
} |
||||
} |
||||
|
||||
} |
||||
@ -1,118 +0,0 @@
@@ -1,118 +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; |
||||
|
||||
import java.lang.reflect.Field; |
||||
|
||||
import org.junit.jupiter.api.Test; |
||||
|
||||
import org.springframework.test.context.bean.override.example.ExampleBeanOverrideAnnotation; |
||||
import org.springframework.test.context.bean.override.example.TestBeanOverrideMetaAnnotation; |
||||
import org.springframework.util.ReflectionUtils; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
import static org.assertj.core.api.Assertions.assertThatRuntimeException; |
||||
|
||||
/** |
||||
* Unit tests for {@link BeanOverrideParsingUtils}. |
||||
* |
||||
* @since 6.2 |
||||
*/ |
||||
class BeanOverrideParsingUtilsTests { |
||||
|
||||
// Metadata built from a String that starts with DUPLICATE_TRIGGER are considered equal
|
||||
private static final String DUPLICATE_TRIGGER1 = ExampleBeanOverrideAnnotation.DUPLICATE_TRIGGER + "-v1"; |
||||
private static final String DUPLICATE_TRIGGER2 = ExampleBeanOverrideAnnotation.DUPLICATE_TRIGGER + "-v2"; |
||||
|
||||
@Test |
||||
void findsOnField() { |
||||
assertThat(BeanOverrideParsingUtils.parse(SingleAnnotationOnField.class)) |
||||
.map(Object::toString) |
||||
.containsExactly("onField"); |
||||
} |
||||
|
||||
@Test |
||||
void allowsMultipleProcessorsOnDifferentElements() { |
||||
assertThat(BeanOverrideParsingUtils.parse(AnnotationsOnMultipleFields.class)) |
||||
.map(Object::toString) |
||||
.containsExactlyInAnyOrder("onField1", "onField2"); |
||||
} |
||||
|
||||
@Test |
||||
void rejectsMultipleAnnotationsOnSameElement() { |
||||
Field field = ReflectionUtils.findField(MultipleAnnotationsOnField.class, "message"); |
||||
assertThatRuntimeException() |
||||
.isThrownBy(() -> BeanOverrideParsingUtils.parse(MultipleAnnotationsOnField.class)) |
||||
.withMessage("Multiple @BeanOverride annotations found on field: " + field); |
||||
} |
||||
|
||||
@Test |
||||
void keepsFirstOccurrenceOfEqualMetadata() { |
||||
assertThat(BeanOverrideParsingUtils.parse(DuplicateConf.class)) |
||||
.map(Object::toString) |
||||
.containsExactly("{DUPLICATE-v1}"); |
||||
} |
||||
|
||||
|
||||
static class SingleAnnotationOnField { |
||||
|
||||
@ExampleBeanOverrideAnnotation("onField") |
||||
String message; |
||||
|
||||
static String onField() { |
||||
return "OK"; |
||||
} |
||||
} |
||||
|
||||
static class MultipleAnnotationsOnField { |
||||
|
||||
@ExampleBeanOverrideAnnotation("foo") |
||||
@TestBeanOverrideMetaAnnotation |
||||
String message; |
||||
|
||||
static String foo() { |
||||
return "foo"; |
||||
} |
||||
} |
||||
|
||||
static class AnnotationsOnMultipleFields { |
||||
|
||||
@ExampleBeanOverrideAnnotation("onField1") |
||||
String message; |
||||
|
||||
@ExampleBeanOverrideAnnotation("onField2") |
||||
String messageOther; |
||||
|
||||
static String onField1() { |
||||
return "OK1"; |
||||
} |
||||
|
||||
static String onField2() { |
||||
return "OK2"; |
||||
} |
||||
} |
||||
|
||||
static class DuplicateConf { |
||||
|
||||
@ExampleBeanOverrideAnnotation(DUPLICATE_TRIGGER1) |
||||
String message1; |
||||
|
||||
@ExampleBeanOverrideAnnotation(DUPLICATE_TRIGGER2) |
||||
String message2; |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,178 @@
@@ -0,0 +1,178 @@
|
||||
/* |
||||
* 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 java.lang.reflect.Field; |
||||
import java.lang.reflect.Method; |
||||
import java.util.List; |
||||
|
||||
import org.junit.jupiter.api.Test; |
||||
|
||||
import org.springframework.beans.factory.annotation.Qualifier; |
||||
import org.springframework.core.ResolvableType; |
||||
import org.springframework.test.context.bean.override.OverrideMetadata; |
||||
import org.springframework.util.ReflectionUtils; |
||||
import org.springframework.util.StringUtils; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
import static org.assertj.core.api.Assertions.assertThatIllegalStateException; |
||||
|
||||
/** |
||||
* Tests for {@link TestBeanOverrideMetadata}. |
||||
* |
||||
* @author Stephane Nicoll |
||||
*/ |
||||
class TestBeanOverrideMetadataTests { |
||||
|
||||
@Test |
||||
void forTestClassSetsNameToNullIfAnnotationNameIsNull() { |
||||
List<OverrideMetadata> list = OverrideMetadata.forTestClass(SampleOneOverride.class); |
||||
assertThat(list).singleElement().satisfies(metadata -> assertThat(metadata.getBeanName()).isNull()); |
||||
} |
||||
|
||||
@Test |
||||
void forTestClassSetsNameToAnnotationName() { |
||||
List<OverrideMetadata> list = OverrideMetadata.forTestClass(SampleOneOverrideWithName.class); |
||||
assertThat(list).singleElement().satisfies(metadata -> assertThat(metadata.getBeanName()).isEqualTo("anotherBean")); |
||||
} |
||||
|
||||
@Test |
||||
void forTestClassWithMissingMethod() { |
||||
assertThatIllegalStateException() |
||||
.isThrownBy(() ->OverrideMetadata.forTestClass(SampleMissingMethod.class)) |
||||
.withMessageStartingWith("Failed to find a static test bean factory method") |
||||
.withMessageContaining("messageTestOverride"); |
||||
} |
||||
|
||||
@Test |
||||
void isEqualToWithSameInstance() { |
||||
TestBeanOverrideMetadata metadata = createMetadata(sampleField("message"), sampleMethod("message")); |
||||
assertThat(metadata).isEqualTo(metadata); |
||||
assertThat(metadata).hasSameHashCodeAs(metadata); |
||||
} |
||||
|
||||
@Test |
||||
void isEqualToWithSameMetadata() { |
||||
TestBeanOverrideMetadata metadata1 = createMetadata(sampleField("message"), sampleMethod("message")); |
||||
TestBeanOverrideMetadata metadata2 = createMetadata(sampleField("message"), sampleMethod("message")); |
||||
assertThat(metadata1).isEqualTo(metadata2); |
||||
assertThat(metadata1).hasSameHashCodeAs(metadata2); |
||||
} |
||||
|
||||
@Test |
||||
void isEqualToWithSameMetadataButDifferentField() { |
||||
TestBeanOverrideMetadata metadata1 = createMetadata(sampleField("message"), sampleMethod("message")); |
||||
TestBeanOverrideMetadata metadata2 = createMetadata(sampleField("message2"), sampleMethod("message")); |
||||
assertThat(metadata1).isEqualTo(metadata2); |
||||
assertThat(metadata1).hasSameHashCodeAs(metadata2); |
||||
} |
||||
|
||||
@Test |
||||
void isNotEqualToWithSameMetadataButDifferentBeanName() { |
||||
TestBeanOverrideMetadata metadata1 = createMetadata(sampleField("message"), sampleMethod("message")); |
||||
TestBeanOverrideMetadata metadata2 = createMetadata(sampleField("message3"), sampleMethod("message")); |
||||
assertThat(metadata1).isNotEqualTo(metadata2); |
||||
} |
||||
|
||||
@Test |
||||
void isNotEqualToWithSameMetadataButDifferentMethod() { |
||||
TestBeanOverrideMetadata metadata1 = createMetadata(sampleField("message"), sampleMethod("message")); |
||||
TestBeanOverrideMetadata metadata2 = createMetadata(sampleField("message"), sampleMethod("description")); |
||||
assertThat(metadata1).isNotEqualTo(metadata2); |
||||
} |
||||
|
||||
@Test |
||||
void isNotEqualToWithSameMetadataButDifferentAnnotations() { |
||||
TestBeanOverrideMetadata metadata1 = createMetadata(sampleField("message"), sampleMethod("message")); |
||||
TestBeanOverrideMetadata metadata2 = createMetadata(sampleField("message4"), sampleMethod("message")); |
||||
assertThat(metadata1).isNotEqualTo(metadata2); |
||||
} |
||||
|
||||
private Field sampleField(String fieldName) { |
||||
Field field = ReflectionUtils.findField(Sample.class, fieldName); |
||||
assertThat(field).isNotNull(); |
||||
return field; |
||||
} |
||||
|
||||
private Method sampleMethod(String noArgMethodName) { |
||||
Method method = ReflectionUtils.findMethod(Sample.class, noArgMethodName); |
||||
assertThat(method).isNotNull(); |
||||
return method; |
||||
} |
||||
|
||||
private TestBeanOverrideMetadata createMetadata(Field field, Method overrideMethod) { |
||||
TestBean annotation = field.getAnnotation(TestBean.class); |
||||
String beanName = (StringUtils.hasText(annotation.value()) ? annotation.value() : null); |
||||
return new TestBeanOverrideMetadata(field, ResolvableType.forClass(field.getType()), beanName, overrideMethod); |
||||
} |
||||
|
||||
static class SampleOneOverride { |
||||
|
||||
@TestBean(methodName = "message") |
||||
String message; |
||||
|
||||
static String message() { |
||||
return "OK"; |
||||
} |
||||
|
||||
} |
||||
|
||||
static class SampleOneOverrideWithName { |
||||
|
||||
@TestBean(name = "anotherBean", methodName = "message") |
||||
String message; |
||||
|
||||
static String message() { |
||||
return "OK"; |
||||
} |
||||
|
||||
} |
||||
|
||||
static class SampleMissingMethod { |
||||
|
||||
@TestBean |
||||
String message; |
||||
|
||||
} |
||||
|
||||
|
||||
@SuppressWarnings("unused") |
||||
static class Sample { |
||||
|
||||
@TestBean |
||||
private String message; |
||||
|
||||
@TestBean |
||||
private String message2; |
||||
|
||||
@TestBean(name = "anotherBean") |
||||
private String message3; |
||||
|
||||
@Qualifier("anotherBean") |
||||
@TestBean |
||||
private String message4; |
||||
|
||||
static String message() { |
||||
return "OK"; |
||||
} |
||||
|
||||
static String description() { |
||||
return message(); |
||||
} |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,151 @@
@@ -0,0 +1,151 @@
|
||||
/* |
||||
* 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.mockito; |
||||
|
||||
import java.io.Externalizable; |
||||
import java.lang.reflect.Field; |
||||
import java.util.List; |
||||
|
||||
import org.junit.jupiter.api.Test; |
||||
import org.mockito.Answers; |
||||
|
||||
import org.springframework.core.ResolvableType; |
||||
import org.springframework.test.context.bean.override.OverrideMetadata; |
||||
import org.springframework.util.ReflectionUtils; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
|
||||
/** |
||||
* Tests for {@link MockitoBeanOverrideMetadata}. |
||||
* |
||||
* @author Stephane Nicoll |
||||
*/ |
||||
class MockitoBeanOverrideMetadataTests { |
||||
|
||||
@Test |
||||
void forTestClassSetsNameToNullIfAnnotationNameIsNull() { |
||||
List<OverrideMetadata> list = OverrideMetadata.forTestClass(SampleOneMock.class); |
||||
assertThat(list).singleElement().satisfies(metadata -> assertThat(metadata.getBeanName()).isNull()); |
||||
} |
||||
|
||||
@Test |
||||
void forTestClassSetsNameToAnnotationName() { |
||||
List<OverrideMetadata> list = OverrideMetadata.forTestClass(SampleOneMockWithName.class); |
||||
assertThat(list).singleElement().satisfies(metadata -> assertThat(metadata.getBeanName()).isEqualTo("anotherService")); |
||||
} |
||||
|
||||
@Test |
||||
void isEqualToWithSameInstance() { |
||||
MockitoBeanOverrideMetadata metadata = createMetadata(sampleField("service")); |
||||
assertThat(metadata).isEqualTo(metadata); |
||||
assertThat(metadata).hasSameHashCodeAs(metadata); |
||||
} |
||||
|
||||
@Test |
||||
void isEqualToWithSameMetadata() { |
||||
MockitoBeanOverrideMetadata metadata = createMetadata(sampleField("service")); |
||||
MockitoBeanOverrideMetadata metadata2 = createMetadata(sampleField("service")); |
||||
assertThat(metadata).isEqualTo(metadata2); |
||||
assertThat(metadata).hasSameHashCodeAs(metadata2); |
||||
} |
||||
|
||||
@Test |
||||
void isEqualToWithSameMetadataButDifferentField() { |
||||
MockitoBeanOverrideMetadata metadata = createMetadata(sampleField("service")); |
||||
MockitoBeanOverrideMetadata metadata2 = createMetadata(sampleField("service2")); |
||||
assertThat(metadata).isEqualTo(metadata2); |
||||
assertThat(metadata).hasSameHashCodeAs(metadata2); |
||||
} |
||||
|
||||
@Test |
||||
void isNotEqualToWithSameMetadataButDifferentBeanName() { |
||||
MockitoBeanOverrideMetadata metadata = createMetadata(sampleField("service")); |
||||
MockitoBeanOverrideMetadata metadata2 = createMetadata(sampleField("service3")); |
||||
assertThat(metadata).isNotEqualTo(metadata2); |
||||
} |
||||
|
||||
@Test |
||||
void isNotEqualToWithSameMetadataButDifferentExtraInterfaces() { |
||||
MockitoBeanOverrideMetadata metadata = createMetadata(sampleField("service")); |
||||
MockitoBeanOverrideMetadata metadata2 = createMetadata(sampleField("service4")); |
||||
assertThat(metadata).isNotEqualTo(metadata2); |
||||
} |
||||
|
||||
@Test |
||||
void isNotEqualToWithSameMetadataButDifferentAnswers() { |
||||
MockitoBeanOverrideMetadata metadata = createMetadata(sampleField("service")); |
||||
MockitoBeanOverrideMetadata metadata2 = createMetadata(sampleField("service5")); |
||||
assertThat(metadata).isNotEqualTo(metadata2); |
||||
} |
||||
|
||||
@Test |
||||
void isNotEqualToWithSameMetadataButDifferentSerializableFlag() { |
||||
MockitoBeanOverrideMetadata metadata = createMetadata(sampleField("service")); |
||||
MockitoBeanOverrideMetadata metadata2 = createMetadata(sampleField("service6")); |
||||
assertThat(metadata).isNotEqualTo(metadata2); |
||||
} |
||||
|
||||
|
||||
private Field sampleField(String fieldName) { |
||||
Field field = ReflectionUtils.findField(Sample.class, fieldName); |
||||
assertThat(field).isNotNull(); |
||||
return field; |
||||
} |
||||
|
||||
private MockitoBeanOverrideMetadata createMetadata(Field field) { |
||||
MockitoBean annotation = field.getAnnotation(MockitoBean.class); |
||||
return new MockitoBeanOverrideMetadata(field, ResolvableType.forClass(field.getType()), annotation); |
||||
} |
||||
|
||||
|
||||
static class SampleOneMock { |
||||
|
||||
@MockitoBean |
||||
String service; |
||||
|
||||
} |
||||
|
||||
static class SampleOneMockWithName { |
||||
|
||||
@MockitoBean(name = "anotherService") |
||||
String service; |
||||
|
||||
} |
||||
|
||||
static class Sample { |
||||
|
||||
@MockitoBean |
||||
private String service; |
||||
|
||||
@MockitoBean |
||||
private String service2; |
||||
|
||||
@MockitoBean(name = "beanToMock") |
||||
private String service3; |
||||
|
||||
@MockitoBean(extraInterfaces = Externalizable.class) |
||||
private String service4; |
||||
|
||||
@MockitoBean(answers = Answers.RETURNS_MOCKS) |
||||
private String service5; |
||||
|
||||
@MockitoBean(serializable = true) |
||||
private String service6; |
||||
|
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,139 @@
@@ -0,0 +1,139 @@
|
||||
/* |
||||
* 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.mockito; |
||||
|
||||
import java.lang.reflect.Field; |
||||
import java.util.List; |
||||
|
||||
import org.junit.jupiter.api.Test; |
||||
|
||||
import org.springframework.core.ResolvableType; |
||||
import org.springframework.test.context.bean.override.OverrideMetadata; |
||||
import org.springframework.util.ReflectionUtils; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
|
||||
/** |
||||
* Tests for {@link MockitoSpyBeanOverrideMetadata}. |
||||
* |
||||
* @author Stephane Nicoll |
||||
*/ |
||||
class MockitoSpyBeanOverrideMetadataTests { |
||||
|
||||
@Test |
||||
void forTestClassSetsNameToNullIfAnnotationNameIsNull() { |
||||
List<OverrideMetadata> list = OverrideMetadata.forTestClass(SampleOneSpy.class); |
||||
assertThat(list).singleElement().satisfies(metadata -> assertThat(metadata.getBeanName()).isNull()); |
||||
} |
||||
|
||||
@Test |
||||
void forTestClassSetsNameToAnnotationName() { |
||||
List<OverrideMetadata> list = OverrideMetadata.forTestClass(SampleOneSpyWithName.class); |
||||
assertThat(list).singleElement().satisfies(metadata -> assertThat(metadata.getBeanName()).isEqualTo("anotherService")); |
||||
} |
||||
|
||||
@Test |
||||
void isEqualToWithSameInstance() { |
||||
MockitoSpyBeanOverrideMetadata metadata = createMetadata(sampleField("service")); |
||||
assertThat(metadata).isEqualTo(metadata); |
||||
assertThat(metadata).hasSameHashCodeAs(metadata); |
||||
} |
||||
|
||||
@Test |
||||
void isEqualToWithSameMetadata() { |
||||
MockitoSpyBeanOverrideMetadata metadata = createMetadata(sampleField("service")); |
||||
MockitoSpyBeanOverrideMetadata metadata2 = createMetadata(sampleField("service")); |
||||
assertThat(metadata).isEqualTo(metadata2); |
||||
assertThat(metadata).hasSameHashCodeAs(metadata2); |
||||
} |
||||
|
||||
@Test |
||||
void isEqualToWithSameMetadataButDifferentField() { |
||||
MockitoSpyBeanOverrideMetadata metadata = createMetadata(sampleField("service")); |
||||
MockitoSpyBeanOverrideMetadata metadata2 = createMetadata(sampleField("service2")); |
||||
assertThat(metadata).isEqualTo(metadata2); |
||||
assertThat(metadata).hasSameHashCodeAs(metadata2); |
||||
} |
||||
|
||||
@Test |
||||
void isNotEqualToWithSameMetadataButDifferentBeanName() { |
||||
MockitoSpyBeanOverrideMetadata metadata = createMetadata(sampleField("service")); |
||||
MockitoSpyBeanOverrideMetadata metadata2 = createMetadata(sampleField("service3")); |
||||
assertThat(metadata).isNotEqualTo(metadata2); |
||||
} |
||||
|
||||
@Test |
||||
void isNotEqualToWithSameMetadataButDifferentReset() { |
||||
MockitoSpyBeanOverrideMetadata metadata = createMetadata(sampleField("service")); |
||||
MockitoSpyBeanOverrideMetadata metadata2 = createMetadata(sampleField("service4")); |
||||
assertThat(metadata).isNotEqualTo(metadata2); |
||||
} |
||||
|
||||
@Test |
||||
void isNotEqualToWithSameMetadataButDifferentProxyTargetAwareFlag() { |
||||
MockitoSpyBeanOverrideMetadata metadata = createMetadata(sampleField("service")); |
||||
MockitoSpyBeanOverrideMetadata metadata2 = createMetadata(sampleField("service5")); |
||||
assertThat(metadata).isNotEqualTo(metadata2); |
||||
} |
||||
|
||||
|
||||
private Field sampleField(String fieldName) { |
||||
Field field = ReflectionUtils.findField(Sample.class, fieldName); |
||||
assertThat(field).isNotNull(); |
||||
return field; |
||||
} |
||||
|
||||
private MockitoSpyBeanOverrideMetadata createMetadata(Field field) { |
||||
MockitoSpyBean annotation = field.getAnnotation(MockitoSpyBean.class); |
||||
return new MockitoSpyBeanOverrideMetadata(field, ResolvableType.forClass(field.getType()), annotation); |
||||
} |
||||
|
||||
|
||||
static class SampleOneSpy { |
||||
|
||||
@MockitoSpyBean |
||||
String service; |
||||
|
||||
} |
||||
|
||||
static class SampleOneSpyWithName { |
||||
|
||||
@MockitoSpyBean(name = "anotherService") |
||||
String service; |
||||
|
||||
} |
||||
|
||||
static class Sample { |
||||
|
||||
@MockitoSpyBean |
||||
private String service; |
||||
|
||||
@MockitoSpyBean |
||||
private String service2; |
||||
|
||||
@MockitoSpyBean(name = "beanToMock") |
||||
private String service3; |
||||
|
||||
@MockitoSpyBean(reset = MockReset.BEFORE) |
||||
private String service4; |
||||
|
||||
@MockitoSpyBean(proxyTargetAware = false) |
||||
private String service5; |
||||
|
||||
} |
||||
|
||||
} |
||||
Loading…
Reference in new issue