Use the field name as a fallback qualifier for Bean Overriding

This commit harmonizes how a candidate bean definition is determined
for overriding using `@TestBean`, `@MockitoBean`, and `@MockitoSpyBean`.

Previously, a qualifier was necessary even if the name of the annotated
field matches the name of a candidate. After this commit, such candidate
will be picked up transparently, the same it is done for regular
autowiring.

This commit also reviews the documentation of the feature as considering
the field means that its name is taken into account to compute a cache
key if by-type lookup is requested.

Closes gh-32939
This commit is contained in:
Stéphane Nicoll
2024-06-10 18:07:36 +02:00
parent 4c7374797e
commit 28f62abda4
10 changed files with 363 additions and 101 deletions

View File

@@ -7,17 +7,27 @@ case, the original bean definition is not replaced, but instead an early instanc
bean is captured and wrapped by the spy. bean is captured and wrapped by the spy.
By default, the annotated field's type is used to search for candidate definitions to By default, the annotated field's type is used to search for candidate definitions to
override, but note that `@Qualifier` annotations are also taken into account for the override. If multiple candidates match, the usual `@Qualifier` can be provided to
purpose of matching. Users can also make things entirely explicit by specifying a bean narrow the candidate to override. Alternatively, a candidate whose bean definition name
`name` in the annotation. matches the name of the field will match.
To use a by-name override rather than a by-type override, specify the `name` attribute
of the annotation.
[WARNING]
====
The qualifiers, including the name of the field are used to determine if a separate
`ApplicationContext` needs to be created. If you are using this feature to mock or
spy the same bean in several tests, make sure to name the field consistently to avoid
creating unnecessary contexts.
====
Each annotation also defines Mockito-specific attributes to fine-tune the mocking details. Each annotation also defines Mockito-specific attributes to fine-tune the mocking details.
The `@MockitoBean` annotation uses the `REPLACE_OR_CREATE_DEFINITION` The `@MockitoBean` annotation uses the `REPLACE_OR_CREATE_DEFINITION`
xref:testing/testcontext-framework/bean-overriding.adoc#testcontext-bean-overriding-custom[strategy for test bean overriding]. xref:testing/testcontext-framework/bean-overriding.adoc#testcontext-bean-overriding-custom[strategy for test bean overriding].
It requires that at most one matching candidate definition exists if a bean name If no definition matches, then a definition is created on-the-fly.
is specified, or exactly one if no bean name is specified.
The `@MockitoSpyBean` annotation uses the `WRAP_BEAN` The `@MockitoSpyBean` annotation uses the `WRAP_BEAN`
xref:testing/testcontext-framework/bean-overriding.adoc#testcontext-bean-overriding-custom[strategy], xref:testing/testcontext-framework/bean-overriding.adoc#testcontext-bean-overriding-custom[strategy],
@@ -25,8 +35,7 @@ and the original instance is wrapped in a Mockito spy.
It requires that exactly one candidate definition exists. It requires that exactly one candidate definition exists.
The following example shows how to configure the bean name via `@MockitoBean` and The following example shows how to use the default behavior of the `@MockitoBean` annotation:
`@MockitoSpyBean`:
[tabs] [tabs]
====== ======
@@ -35,17 +44,80 @@ Java::
[source,java,indent=0,subs="verbatim,quotes",role="primary"] [source,java,indent=0,subs="verbatim,quotes",role="primary"]
---- ----
class OverrideBeanTests { class OverrideBeanTests {
@MockitoBean // <1>
@MockitoBean(name = "service1") // <1> private CustomService customService;
private CustomService mockService;
@MockitoSpyBean(name = "service2") // <2>
private CustomService spyService; // <3>
// test case body... // test case body...
} }
---- ----
<1> Mark `mockService` as a Mockito mock override of bean `service1` in this test class. <1> Replace the bean with type `CustomService` with a Mockito `mock`.
<2> Mark `spyService` as a Mockito spy override of bean `service2` in this test class. ======
<3> The fields will be injected with the Mockito mock and spy, respectively.
In the example above, we are creating a mock for `CustomService`. If more that
one bean with such type exist, the bean named `customService` is considered. Otherwise,
the test will fail and you will need to provide a qualifier of some sort to identify which
of the `CustomService` beans you want to override. If no such bean exists, a bean
definition will be created with an auto-generated bean name.
The following example uses a by-name lookup, rather than a by-type lookup:
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
class OverrideBeanTests {
@MockitoBean(name = "service") // <1>
private CustomService customService;
// test case body...
}
----
<1> Replace the bean named `service` with a Mockito `mock`.
======
If no bean definition named `service` exists, one is created.
The following example shows how to use the default behavior of the `@MockitoSpyBean` annotation:
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
class OverrideBeanTests {
@MockitoSpyBean // <1>
private CustomService customService;
// test case body...
}
----
<1> Wrap the bean with type `CustomService` with a Mockito `spy`.
======
In the example above, we are wrapping the bean with type `CustomService`. If more that
one bean with such type exist, the bean named `customService` is considered. Otherwise,
the test will fail and you will need to provide a qualifier of some sort to identify which
of the `CustomService` beans you want to spy.
The following example uses a by-name lookup, rather than a by-type lookup:
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
class OverrideBeanTests {
@MockitoSpyBean(name = "service") // <1>
private CustomService customService;
// test case body...
}
----
<1> Wrap the bean named `service` with a Mockito `spy`.
====== ======

View File

@@ -11,11 +11,21 @@ with the type of the bean to override is expected. To make things more explicit,
you'd rather use a different name, the annotation allows for a specific method name to you'd rather use a different name, the annotation allows for a specific method name to
be provided. be provided.
By default, the annotated field's type is used to search for candidate definitions to
override. If multiple candidates match, the usual `@Qualifier` can be provided to
narrow the candidate to override. Alternatively, a candidate whose bean definition name
matches the name of the field will match.
By default, the annotated field's type is used to search for candidate definitions to override. To use a by-name override rather than a by-type override, specify the `name` attribute
In that case it is required that exactly one definition matches, but note that `@Qualifier` of the annotation.
annotations are also taken into account for the purpose of matching.
Users can also make things entirely explicit by specifying a bean `name` in the annotation. [WARNING]
====
The qualifiers, including the name of the field are used to determine if a separate
`ApplicationContext` needs to be created. If you are using this feature to override
the same bean in several tests, make sure to name the field consistently to avoid
creating unnecessary contexts.
====
The following example shows how to use the default behavior of the `@TestBean` annotation: The following example shows how to use the default behavior of the `@TestBean` annotation:
@@ -27,11 +37,11 @@ Java::
---- ----
class OverrideBeanTests { class OverrideBeanTests {
@TestBean // <1> @TestBean // <1>
private CustomService service; private CustomService customService;
// test case body... // test case body...
private static CustomService service() { // <2> private static CustomService customService() { // <2>
return new MyFakeCustomService(); return new MyFakeCustomService();
} }
} }
@@ -40,8 +50,13 @@ Java::
<2> The result of this static method will be used as the instance and injected into the field. <2> The result of this static method will be used as the instance and injected into the field.
====== ======
In the example above, we are overriding the bean with type `CustomService`. If more that
one bean with such type exist, the bean named `customService` is considered. Otherwise,
the test will fail and you will need to provide a qualifier of some sort to identify which
of the `CustomService` beans you want to override.
The following example shows how to fully configure the `@TestBean` annotation:
The following example uses a by-name lookup, rather than a by-type lookup:
[tabs] [tabs]
====== ======
@@ -51,7 +66,7 @@ Java::
---- ----
class OverrideBeanTests { class OverrideBeanTests {
@TestBean(name = "service", methodName = "createCustomService") // <1> @TestBean(name = "service", methodName = "createCustomService") // <1>
private CustomService service; private CustomService customService;
// test case body... // test case body...

View File

@@ -259,6 +259,13 @@ class BeanOverrideBeanFactoryPostProcessor implements BeanFactoryPostProcessor,
else { else {
beans.removeIf(ScopedProxyUtils::isScopedTarget); beans.removeIf(ScopedProxyUtils::isScopedTarget);
} }
// In case of multiple matches, last resort fallback on the field's name
if (beans.size() > 1) {
String fieldName = metadata.getField().getName();
if (beans.contains(fieldName)) {
return Set.of(fieldName);
}
}
return beans; return beans;
} }

View File

@@ -167,16 +167,24 @@ public abstract class OverrideMetadata {
return false; return false;
} }
OverrideMetadata that = (OverrideMetadata) obj; OverrideMetadata that = (OverrideMetadata) obj;
return Objects.equals(this.beanType.getType(), that.beanType.getType()) && if (!Objects.equals(this.beanType.getType(), that.beanType.getType()) ||
Objects.equals(this.beanName, that.beanName) && !Objects.equals(this.beanName, that.beanName) ||
Objects.equals(this.strategy, that.strategy) && !Objects.equals(this.strategy, that.strategy)) {
return false;
}
if (this.beanName != null) {
return true;
}
// by type lookup
return Objects.equals(this.field.getName(), that.field.getName()) &&
Arrays.equals(this.field.getAnnotations(), that.field.getAnnotations()); Arrays.equals(this.field.getAnnotations(), that.field.getAnnotations());
} }
@Override @Override
public int hashCode() { public int hashCode() {
return Objects.hash(this.beanType.getType(), this.beanName, this.strategy, int hash = Objects.hash(this.beanType.getType(), this.beanName, this.strategy);
Arrays.hashCode(this.field.getAnnotations())); return (this.beanName != null ? hash : hash +
Objects.hash(this.field.getName(), Arrays.hashCode(this.field.getAnnotations())));
} }
@Override @Override

View File

@@ -55,18 +55,16 @@ import static org.mockito.Mockito.mock;
class BeanOverrideBeanFactoryPostProcessorTests { class BeanOverrideBeanFactoryPostProcessorTests {
@Test @Test
void canReplaceExistingBeanDefinitions() { void replaceBeanByNameWithMatchingBeanDefinition() {
AnnotationConfigApplicationContext context = createContext(CaseByNameAndByType.class); AnnotationConfigApplicationContext context = createContext(CaseByName.class);
context.registerBean("descriptionBean", String.class, () -> "Original"); context.registerBean("descriptionBean", String.class, () -> "Original");
context.registerBean("someInteger", Integer.class, () -> 1);
context.refresh(); context.refresh();
assertThat(context.getBean("descriptionBean")).isEqualTo("overridden"); assertThat(context.getBean("descriptionBean")).isEqualTo("overridden");
assertThat(context.getBean("someInteger")).isSameAs(42);
} }
@Test @Test
void cannotReplaceIfNoBeanNameMatching() { void replaceBeanByNameWithoutMatchingBeanDefinitionFails() {
AnnotationConfigApplicationContext context = createContext(CaseByName.class); AnnotationConfigApplicationContext context = createContext(CaseByName.class);
assertThatIllegalStateException() assertThatIllegalStateException()
@@ -76,40 +74,18 @@ class BeanOverrideBeanFactoryPostProcessorTests {
} }
@Test @Test
void cannotReplaceIfNoBeanTypeMatching() { void replaceBeanByNameWithMatchingBeanDefinitionAndWrongTypeFails() {
AnnotationConfigApplicationContext context = createContext(CaseByType.class); AnnotationConfigApplicationContext context = createContext(CaseByName.class);
context.registerBean("descriptionBean", Integer.class, () -> -1);
assertThatIllegalStateException() assertThatIllegalStateException()
.isThrownBy(context::refresh) .isThrownBy(context::refresh)
.withMessage("Unable to override bean: no bean definitions of type java.lang.Integer " + .withMessage("Unable to override bean 'descriptionBean': there is no bean definition " +
"(as required by annotated field 'CaseByType.counter')"); "to replace with that name of type java.lang.String");
} }
@Test @Test
void canReplaceExistingBeanDefinitionsWithCreateReplaceStrategy() { void replaceBeanByNameCanOverrideBeanProducedByFactoryBeanWithClassObjectTypeAttribute() {
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("descriptionBean")).isEqualTo("overridden");
assertThat(context.getBean("someInteger")).isEqualTo(42);
}
@Test
void canCreateIfOriginalMissingWithCreateReplaceStrategy() {
AnnotationConfigApplicationContext context = createContext(CaseByNameAndByTypeWithReplaceOrCreateStrategy.class);
context.refresh();
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 = prepareContextWithFactoryBean(CharSequence.class); AnnotationConfigApplicationContext context = prepareContextWithFactoryBean(CharSequence.class);
context.refresh(); context.refresh();
@@ -117,7 +93,7 @@ class BeanOverrideBeanFactoryPostProcessorTests {
} }
@Test @Test
void canOverrideBeanProducedByFactoryBeanWithResolvableTypeObjectTypeAttribute() { void replaceBeanByNameCanOverrideBeanProducedByFactoryBeanWithResolvableTypeObjectTypeAttribute() {
AnnotationConfigApplicationContext context = prepareContextWithFactoryBean(ResolvableType.forClass(CharSequence.class)); AnnotationConfigApplicationContext context = prepareContextWithFactoryBean(ResolvableType.forClass(CharSequence.class));
context.refresh(); context.refresh();
@@ -133,6 +109,84 @@ class BeanOverrideBeanFactoryPostProcessorTests {
return context; return context;
} }
@Test
void replaceBeanByTypeWithSingleMatchingBean() {
AnnotationConfigApplicationContext context = createContext(CaseByType.class);
context.registerBean("someInteger", Integer.class, () -> 1);
context.refresh();
assertThat(context.getBean("someInteger")).isEqualTo(42);
}
@Test
void replaceBeanByTypeWithoutMatchingBeanFails() {
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 replaceBeanByTypeWithMultipleMatchesAndNoQualifierFails() {
AnnotationConfigApplicationContext context = createContext(CaseByType.class);
context.registerBean("someInteger", Integer.class, () -> 1);
context.registerBean("anotherInteger", Integer.class, () -> 2);
assertThatIllegalStateException()
.isThrownBy(context::refresh)
.withMessage("Unable to select a bean definition to override: found 2 bean definitions " +
"of type java.lang.Integer (as required by annotated field 'CaseByType.counter'): " +
"[someInteger, anotherInteger]");
}
@Test
void replaceBeanByTypeWithMultipleMatchesAndFieldNameQualifierMatches() {
AnnotationConfigApplicationContext context = createContext(CaseByType.class);
context.registerBean("counter", Integer.class, () -> 1);
context.registerBean("someInteger", Integer.class, () -> 2);
context.refresh();
assertThat(context.getBean("counter")).isSameAs(42);
}
@Test
void createOrReplaceBeanByNameWithMatchingBeanDefinition() {
AnnotationConfigApplicationContext context = createContext(CaseByNameWithReplaceOrCreateStrategy.class);
context.registerBean("descriptionBean", String.class, () -> "Original");
context.refresh();
assertThat(context.getBean("descriptionBean")).isEqualTo("overridden");
}
@Test
void createOrReplaceBeanByNameWithoutMatchingDefinitionCreatesBeanDefinition() {
AnnotationConfigApplicationContext context = createContext(CaseByNameWithReplaceOrCreateStrategy.class);
context.refresh();
assertThat(context.getBean("descriptionBean")).isEqualTo("overridden");
}
@Test
void createOrReplaceBeanByTypeWithMatchingBean() {
AnnotationConfigApplicationContext context = createContext(CaseByTypeWithReplaceOrCreateStrategy.class);
context.registerBean("someBean", String.class, () -> "Original");
context.refresh();
assertThat(context.getBean("someBean")).isEqualTo("overridden");
}
@Test
void createOrReplaceBeanByTypeWithoutMatchingDefinitionCreatesBeanDefinition() {
AnnotationConfigApplicationContext context = createContext(CaseByTypeWithReplaceOrCreateStrategy.class);
context.refresh();
String generatedBeanName = "java.lang.String#0";
assertThat(context.getBeanDefinitionNames()).contains(generatedBeanName);
assertThat(context.getBean(generatedBeanName)).isEqualTo("overridden");
}
@Test @Test
void postProcessorShouldNotTriggerEarlyInitialization() { void postProcessorShouldNotTriggerEarlyInitialization() {
AnnotationConfigApplicationContext context = createContext(CaseByTypeWithReplaceOrCreateStrategy.class); AnnotationConfigApplicationContext context = createContext(CaseByTypeWithReplaceOrCreateStrategy.class);
@@ -216,14 +270,11 @@ class BeanOverrideBeanFactoryPostProcessorTests {
} }
static class CaseByNameAndByType { static class CaseByNameWithReplaceOrCreateStrategy {
@DummyBean(beanName = "descriptionBean") @DummyBean(beanName = "descriptionBean", strategy = BeanOverrideStrategy.REPLACE_OR_CREATE_DEFINITION)
private String description; private String description;
@DummyBean
private Integer counter;
} }
static class CaseByTypeWithReplaceOrCreateStrategy { static class CaseByTypeWithReplaceOrCreateStrategy {

View File

@@ -68,9 +68,9 @@ public class OverrideMetadataTests {
List<OverrideMetadata> overrideMetadata = OverrideMetadata.forTestClass(MultipleAnnotationsDuplicate.class); List<OverrideMetadata> overrideMetadata = OverrideMetadata.forTestClass(MultipleAnnotationsDuplicate.class);
assertThat(overrideMetadata).hasSize(2) assertThat(overrideMetadata).hasSize(2)
.anySatisfy(hasTestBeanMetadata( .anySatisfy(hasTestBeanMetadata(
field(MultipleAnnotationsDuplicate.class, "message1"), String.class, null)) field(MultipleAnnotationsDuplicate.class, "message1"), String.class, "messageBean"))
.anySatisfy(hasTestBeanMetadata( .anySatisfy(hasTestBeanMetadata(
field(MultipleAnnotationsDuplicate.class, "message2"), String.class, null)); field(MultipleAnnotationsDuplicate.class, "message2"), String.class, "messageBean"));
assertThat(new HashSet<>(overrideMetadata)).hasSize(1); assertThat(new HashSet<>(overrideMetadata)).hasSize(1);
} }
@@ -127,6 +127,14 @@ public class OverrideMetadataTests {
assertThat(metadata).hasSameHashCodeAs(metadata2); assertThat(metadata).hasSameHashCodeAs(metadata2);
} }
@Test
void isEqualToWithByNameLookupAndDifferentFieldNames() {
OverrideMetadata metadata = createMetadata(field(ConfigA.class, "noQualifier"), "beanToOverride");
OverrideMetadata metadata2 = createMetadata(field(ConfigB.class, "example"), "beanToOverride");
assertThat(metadata).isEqualTo(metadata2);
assertThat(metadata).hasSameHashCodeAs(metadata2);
}
@Test @Test
void isEqualToWithSameMetadataAndSameQualifierValues() { void isEqualToWithSameMetadataAndSameQualifierValues() {
OverrideMetadata metadata = createMetadata(field(ConfigA.class, "directQualifier")); OverrideMetadata metadata = createMetadata(field(ConfigA.class, "directQualifier"));
@@ -149,6 +157,13 @@ public class OverrideMetadataTests {
assertThat(metadata).isNotEqualTo(metadata2); assertThat(metadata).isNotEqualTo(metadata2);
} }
@Test
void isNotEqualToWithByTypeLookupAndDifferentFieldNames() {
OverrideMetadata metadata = createMetadata(field(ConfigA.class, "noQualifier"));
OverrideMetadata metadata2 = createMetadata(field(ConfigB.class, "example"));
assertThat(metadata).isNotEqualTo(metadata2);
}
private OverrideMetadata createMetadata(Field field) { private OverrideMetadata createMetadata(Field field) {
return createMetadata(field, null); return createMetadata(field, null);
} }
@@ -195,10 +210,10 @@ public class OverrideMetadataTests {
static class MultipleAnnotationsDuplicate { static class MultipleAnnotationsDuplicate {
@DummyBean @DummyBean(beanName = "messageBean")
String message1; String message1;
@DummyBean @DummyBean(beanName = "messageBean")
String message2; String message2;
} }
@@ -233,6 +248,8 @@ public class OverrideMetadataTests {
private ExampleService noQualifier; private ExampleService noQualifier;
private ExampleService example;
@Qualifier("test") @Qualifier("test")
private ExampleService directQualifier; private ExampleService directQualifier;

View File

@@ -74,13 +74,20 @@ class TestBeanOverrideMetadataTests {
} }
@Test @Test
void isEqualToWithSameMetadataButDifferentField() { void isEqualToWithSameMetadataByNameLookupAndDifferentField() {
TestBeanOverrideMetadata metadata1 = createMetadata(sampleField("message"), sampleMethod("message")); TestBeanOverrideMetadata metadata1 = createMetadata(sampleField("message3"), sampleMethod("message"));
TestBeanOverrideMetadata metadata2 = createMetadata(sampleField("message2"), sampleMethod("message")); TestBeanOverrideMetadata metadata2 = createMetadata(sampleField("message4"), sampleMethod("message"));
assertThat(metadata1).isEqualTo(metadata2); assertThat(metadata1).isEqualTo(metadata2);
assertThat(metadata1).hasSameHashCodeAs(metadata2); assertThat(metadata1).hasSameHashCodeAs(metadata2);
} }
@Test
void isNotEqualToWithSameMetadataByTypeLookupAndDifferentField() {
TestBeanOverrideMetadata metadata1 = createMetadata(sampleField("message"), sampleMethod("message"));
TestBeanOverrideMetadata metadata2 = createMetadata(sampleField("message2"), sampleMethod("message"));
assertThat(metadata1).isNotEqualTo(metadata2);
}
@Test @Test
void isNotEqualToWithSameMetadataButDifferentBeanName() { void isNotEqualToWithSameMetadataButDifferentBeanName() {
TestBeanOverrideMetadata metadata1 = createMetadata(sampleField("message"), sampleMethod("message")); TestBeanOverrideMetadata metadata1 = createMetadata(sampleField("message"), sampleMethod("message"));
@@ -98,7 +105,7 @@ class TestBeanOverrideMetadataTests {
@Test @Test
void isNotEqualToWithSameMetadataButDifferentAnnotations() { void isNotEqualToWithSameMetadataButDifferentAnnotations() {
TestBeanOverrideMetadata metadata1 = createMetadata(sampleField("message"), sampleMethod("message")); TestBeanOverrideMetadata metadata1 = createMetadata(sampleField("message"), sampleMethod("message"));
TestBeanOverrideMetadata metadata2 = createMetadata(sampleField("message4"), sampleMethod("message")); TestBeanOverrideMetadata metadata2 = createMetadata(sampleField("message5"), sampleMethod("message"));
assertThat(metadata1).isNotEqualTo(metadata2); assertThat(metadata1).isNotEqualTo(metadata2);
} }
@@ -116,7 +123,7 @@ class TestBeanOverrideMetadataTests {
private TestBeanOverrideMetadata createMetadata(Field field, Method overrideMethod) { private TestBeanOverrideMetadata createMetadata(Field field, Method overrideMethod) {
TestBean annotation = field.getAnnotation(TestBean.class); TestBean annotation = field.getAnnotation(TestBean.class);
String beanName = (StringUtils.hasText(annotation.value()) ? annotation.value() : null); String beanName = (StringUtils.hasText(annotation.name()) ? annotation.name() : null);
return new TestBeanOverrideMetadata(field, ResolvableType.forClass(field.getType()), beanName, overrideMethod); return new TestBeanOverrideMetadata(field, ResolvableType.forClass(field.getType()), beanName, overrideMethod);
} }
@@ -162,9 +169,12 @@ class TestBeanOverrideMetadataTests {
@TestBean(name = "anotherBean") @TestBean(name = "anotherBean")
private String message3; private String message3;
@TestBean(name = "anotherBean")
private String message4;
@Qualifier("anotherBean") @Qualifier("anotherBean")
@TestBean @TestBean
private String message4; private String message5;
static String message() { static String message() {
return "OK"; return "OK";

View File

@@ -34,28 +34,48 @@ class MockitoBeanContextCustomizerEqualityTests {
@Test @Test
void contextCustomizerWithSameMockInDifferentClassIsEqual() { void contextCustomizerWithSameMockByNameInDifferentClassIsEqual() {
assertThat(createContextCustomizer(Case1.class)).isEqualTo(createContextCustomizer(Case2.class)); assertThat(createContextCustomizer(Case1ByName.class)).isEqualTo(createContextCustomizer(Case2ByName.class));
} }
@Test @Test
void contextCustomizerWithSameSpyInDifferentClassIsEqual() { void contextCustomizerWithSameMockByTypeInDifferentClassIsEqual() {
assertThat(createContextCustomizer(Case4.class)).isEqualTo(createContextCustomizer(Case5.class)); assertThat(createContextCustomizer(Case1ByType.class)).isEqualTo(createContextCustomizer(Case2ByTypeSameFieldName.class));
}
@Test
void contextCustomizerWithSameMockByTypeAndDifferentFieldNamesAreNotEqual() {
assertThat(createContextCustomizer(Case1ByType.class)).isNotEqualTo(createContextCustomizer(Case2ByType.class));
}
@Test
void contextCustomizerWithSameSpyByNameInDifferentClassIsEqual() {
assertThat(createContextCustomizer(Case4ByName.class)).isEqualTo(createContextCustomizer(Case5ByName.class));
}
@Test
void contextCustomizerWithSameSpyByTypeInDifferentClassIsEqual() {
assertThat(createContextCustomizer(Case4ByType.class)).isEqualTo(createContextCustomizer(Case5ByTypeSameFieldName.class));
}
@Test
void contextCustomizerWithSameSpyByTypeAndDifferentFieldNamesAreNotEqual() {
assertThat(createContextCustomizer(Case4ByType.class)).isNotEqualTo(createContextCustomizer(Case5ByType.class));
} }
@Test @Test
void contextCustomizerWithSimilarMockButDifferentAnswersIsNotEqual() { void contextCustomizerWithSimilarMockButDifferentAnswersIsNotEqual() {
assertThat(createContextCustomizer(Case1.class)).isNotEqualTo(createContextCustomizer(Case3.class)); assertThat(createContextCustomizer(Case1ByType.class)).isNotEqualTo(createContextCustomizer(Case3.class));
} }
@Test @Test
void contextCustomizerWithSimilarSpyButDifferentProxyTargetClassFlagIsNotEqual() { void contextCustomizerWithSimilarSpyButDifferentProxyTargetClassFlagIsNotEqual() {
assertThat(createContextCustomizer(Case5.class)).isNotEqualTo(createContextCustomizer(Case6.class)); assertThat(createContextCustomizer(Case5ByType.class)).isNotEqualTo(createContextCustomizer(Case6.class));
} }
@Test @Test
void contextCustomizerWithMockAndSpyAreNotEqual() { void contextCustomizerWithMockAndSpyAreNotEqual() {
assertThat(createContextCustomizer(Case1.class)).isNotEqualTo(createContextCustomizer(Case4.class)); assertThat(createContextCustomizer(Case1ByType.class)).isNotEqualTo(createContextCustomizer(Case4ByType.class));
} }
private ContextCustomizer createContextCustomizer(Class<?> testClass) { private ContextCustomizer createContextCustomizer(Class<?> testClass) {
@@ -64,20 +84,41 @@ class MockitoBeanContextCustomizerEqualityTests {
return customizer; return customizer;
} }
static class Case1 { static class Case1ByName {
@MockitoBean(name = "serviceBean")
private String exampleService;
}
static class Case1ByType {
@MockitoBean @MockitoBean
private String exampleService; private String exampleService;
} }
static class Case2 { static class Case2ByName {
@MockitoBean(name = "serviceBean")
private String serviceToMock;
}
static class Case2ByType {
@MockitoBean @MockitoBean
private String serviceToMock; private String serviceToMock;
} }
static class Case2ByTypeSameFieldName {
@MockitoBean
private String exampleService;
}
static class Case3 { static class Case3 {
@MockitoBean(answers = Answers.RETURNS_MOCKS) @MockitoBean(answers = Answers.RETURNS_MOCKS)
@@ -85,20 +126,41 @@ class MockitoBeanContextCustomizerEqualityTests {
} }
static class Case4 { static class Case4ByName {
@MockitoSpyBean(name = "serviceBean")
private String exampleService;
}
static class Case4ByType {
@MockitoSpyBean @MockitoSpyBean
private String exampleService; private String exampleService;
} }
static class Case5 { static class Case5ByName {
@MockitoSpyBean(name = "serviceBean")
private String serviceToMock;
}
static class Case5ByType {
@MockitoSpyBean @MockitoSpyBean
private String serviceToMock; private String serviceToMock;
} }
static class Case5ByTypeSameFieldName {
@MockitoSpyBean
private String exampleService;
}
static class Case6 { static class Case6 {
@MockitoSpyBean(proxyTargetAware = false) @MockitoSpyBean(proxyTargetAware = false)

View File

@@ -64,9 +64,16 @@ class MockitoBeanOverrideMetadataTests {
} }
@Test @Test
void isEqualToWithSameMetadataButDifferentField() { void isNotEqualEqualToByTypeLookupWithSameMetadataButDifferentField() {
MockitoBeanOverrideMetadata metadata = createMetadata(sampleField("service")); MockitoBeanOverrideMetadata metadata = createMetadata(sampleField("service"));
MockitoBeanOverrideMetadata metadata2 = createMetadata(sampleField("service2")); MockitoBeanOverrideMetadata metadata2 = createMetadata(sampleField("service2"));
assertThat(metadata).isNotEqualTo(metadata2);
}
@Test
void isEqualEqualToByNameLookupWithSameMetadataButDifferentField() {
MockitoBeanOverrideMetadata metadata = createMetadata(sampleField("service3"));
MockitoBeanOverrideMetadata metadata2 = createMetadata(sampleField("service4"));
assertThat(metadata).isEqualTo(metadata2); assertThat(metadata).isEqualTo(metadata2);
assertThat(metadata).hasSameHashCodeAs(metadata2); assertThat(metadata).hasSameHashCodeAs(metadata2);
} }
@@ -81,21 +88,21 @@ class MockitoBeanOverrideMetadataTests {
@Test @Test
void isNotEqualToWithSameMetadataButDifferentExtraInterfaces() { void isNotEqualToWithSameMetadataButDifferentExtraInterfaces() {
MockitoBeanOverrideMetadata metadata = createMetadata(sampleField("service")); MockitoBeanOverrideMetadata metadata = createMetadata(sampleField("service"));
MockitoBeanOverrideMetadata metadata2 = createMetadata(sampleField("service4")); MockitoBeanOverrideMetadata metadata2 = createMetadata(sampleField("service5"));
assertThat(metadata).isNotEqualTo(metadata2); assertThat(metadata).isNotEqualTo(metadata2);
} }
@Test @Test
void isNotEqualToWithSameMetadataButDifferentAnswers() { void isNotEqualToWithSameMetadataButDifferentAnswers() {
MockitoBeanOverrideMetadata metadata = createMetadata(sampleField("service")); MockitoBeanOverrideMetadata metadata = createMetadata(sampleField("service"));
MockitoBeanOverrideMetadata metadata2 = createMetadata(sampleField("service5")); MockitoBeanOverrideMetadata metadata2 = createMetadata(sampleField("service6"));
assertThat(metadata).isNotEqualTo(metadata2); assertThat(metadata).isNotEqualTo(metadata2);
} }
@Test @Test
void isNotEqualToWithSameMetadataButDifferentSerializableFlag() { void isNotEqualToWithSameMetadataButDifferentSerializableFlag() {
MockitoBeanOverrideMetadata metadata = createMetadata(sampleField("service")); MockitoBeanOverrideMetadata metadata = createMetadata(sampleField("service"));
MockitoBeanOverrideMetadata metadata2 = createMetadata(sampleField("service6")); MockitoBeanOverrideMetadata metadata2 = createMetadata(sampleField("service7"));
assertThat(metadata).isNotEqualTo(metadata2); assertThat(metadata).isNotEqualTo(metadata2);
} }
@@ -137,15 +144,18 @@ class MockitoBeanOverrideMetadataTests {
@MockitoBean(name = "beanToMock") @MockitoBean(name = "beanToMock")
private String service3; private String service3;
@MockitoBean(extraInterfaces = Externalizable.class) @MockitoBean(name = "beanToMock")
private String service4; private String service4;
@MockitoBean(answers = Answers.RETURNS_MOCKS) @MockitoBean(extraInterfaces = Externalizable.class)
private String service5; private String service5;
@MockitoBean(serializable = true) @MockitoBean(answers = Answers.RETURNS_MOCKS)
private String service6; private String service6;
@MockitoBean(serializable = true)
private String service7;
} }
} }

View File

@@ -62,9 +62,16 @@ class MockitoSpyBeanOverrideMetadataTests {
} }
@Test @Test
void isEqualToWithSameMetadataButDifferentField() { void isNotEqualToByTypeLookupWithSameMetadataButDifferentField() {
MockitoSpyBeanOverrideMetadata metadata = createMetadata(sampleField("service")); MockitoSpyBeanOverrideMetadata metadata = createMetadata(sampleField("service"));
MockitoSpyBeanOverrideMetadata metadata2 = createMetadata(sampleField("service2")); MockitoSpyBeanOverrideMetadata metadata2 = createMetadata(sampleField("service2"));
assertThat(metadata).isNotEqualTo(metadata2);
}
@Test
void isEqualToByNameLookupWithSameMetadataButDifferentField() {
MockitoSpyBeanOverrideMetadata metadata = createMetadata(sampleField("service3"));
MockitoSpyBeanOverrideMetadata metadata2 = createMetadata(sampleField("service4"));
assertThat(metadata).isEqualTo(metadata2); assertThat(metadata).isEqualTo(metadata2);
assertThat(metadata).hasSameHashCodeAs(metadata2); assertThat(metadata).hasSameHashCodeAs(metadata2);
} }
@@ -79,14 +86,14 @@ class MockitoSpyBeanOverrideMetadataTests {
@Test @Test
void isNotEqualToWithSameMetadataButDifferentReset() { void isNotEqualToWithSameMetadataButDifferentReset() {
MockitoSpyBeanOverrideMetadata metadata = createMetadata(sampleField("service")); MockitoSpyBeanOverrideMetadata metadata = createMetadata(sampleField("service"));
MockitoSpyBeanOverrideMetadata metadata2 = createMetadata(sampleField("service4")); MockitoSpyBeanOverrideMetadata metadata2 = createMetadata(sampleField("service5"));
assertThat(metadata).isNotEqualTo(metadata2); assertThat(metadata).isNotEqualTo(metadata2);
} }
@Test @Test
void isNotEqualToWithSameMetadataButDifferentProxyTargetAwareFlag() { void isNotEqualToWithSameMetadataButDifferentProxyTargetAwareFlag() {
MockitoSpyBeanOverrideMetadata metadata = createMetadata(sampleField("service")); MockitoSpyBeanOverrideMetadata metadata = createMetadata(sampleField("service"));
MockitoSpyBeanOverrideMetadata metadata2 = createMetadata(sampleField("service5")); MockitoSpyBeanOverrideMetadata metadata2 = createMetadata(sampleField("service6"));
assertThat(metadata).isNotEqualTo(metadata2); assertThat(metadata).isNotEqualTo(metadata2);
} }
@@ -128,12 +135,15 @@ class MockitoSpyBeanOverrideMetadataTests {
@MockitoSpyBean(name = "beanToMock") @MockitoSpyBean(name = "beanToMock")
private String service3; private String service3;
@MockitoSpyBean(reset = MockReset.BEFORE) @MockitoSpyBean(name = "beanToMock")
private String service4; private String service4;
@MockitoSpyBean(proxyTargetAware = false) @MockitoSpyBean(reset = MockReset.BEFORE)
private String service5; private String service5;
@MockitoSpyBean(proxyTargetAware = false)
private String service6;
} }
} }