Harmonize OverrideMetadata implementations

This commit makes sure that each OverrideMetadata implementation is
a top level class with a consistent name.
This commit is contained in:
Stéphane Nicoll
2024-06-07 14:53:24 +02:00
parent e8c122c00a
commit 8b66eca932
8 changed files with 172 additions and 142 deletions

View File

@@ -0,0 +1,93 @@
/*
* 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.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Objects;
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.ReflectionUtils;
import org.springframework.util.StringUtils;
/**
* {@link OverrideMetadata} implementation for {@link TestBean}.
*
* @author Simon Baslé
* @author Stephane Nicoll
* @since 6.2
*/
final class TestBeanOverrideMetadata extends OverrideMetadata {
private final Method overrideMethod;
private final String beanName;
TestBeanOverrideMetadata(Field field, Method overrideMethod, TestBean overrideAnnotation,
ResolvableType typeToOverride) {
super(field, typeToOverride, BeanOverrideStrategy.REPLACE_DEFINITION);
this.beanName = overrideAnnotation.name();
this.overrideMethod = overrideMethod;
}
@Override
@Nullable
protected String getBeanName() {
return StringUtils.hasText(this.beanName) ? this.beanName : super.getBeanName();
}
@Override
protected Object createOverride(String beanName, @Nullable BeanDefinition existingBeanDefinition,
@Nullable Object existingBeanInstance) {
try {
ReflectionUtils.makeAccessible(this.overrideMethod);
return this.overrideMethod.invoke(null);
}
catch (IllegalAccessException | InvocationTargetException ex) {
throw new IllegalStateException("Failed to invoke bean overriding method " + this.overrideMethod.getName() +
"; a static method with no formal parameters is expected", ex);
}
}
@Override
public boolean equals(@Nullable Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
if (!super.equals(o)) {
return false;
}
TestBeanOverrideMetadata that = (TestBeanOverrideMetadata) o;
return Objects.equals(this.overrideMethod, that.overrideMethod)
&& Objects.equals(this.beanName, that.beanName);
}
@Override
public int hashCode() {
return Objects.hash(super.hashCode(), this.overrideMethod, this.beanName);
}
}

View File

@@ -18,25 +18,18 @@ package org.springframework.test.context.bean.override.convention;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.core.MethodIntrospector;
import org.springframework.core.ResolvableType;
import org.springframework.lang.Nullable;
import org.springframework.test.context.TestContextAnnotationUtils;
import org.springframework.test.context.bean.override.BeanOverrideProcessor;
import org.springframework.test.context.bean.override.BeanOverrideStrategy;
import org.springframework.test.context.bean.override.OverrideMetadata;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.ReflectionUtils.MethodFilter;
import org.springframework.util.StringUtils;
@@ -143,61 +136,4 @@ class TestBeanOverrideProcessor implements BeanOverrideProcessor {
return methods;
}
static final class TestBeanOverrideMetadata extends OverrideMetadata {
private final Method overrideMethod;
private final String beanName;
public TestBeanOverrideMetadata(Field field, Method overrideMethod, TestBean overrideAnnotation,
ResolvableType typeToOverride) {
super(field, typeToOverride, BeanOverrideStrategy.REPLACE_DEFINITION);
this.beanName = overrideAnnotation.name();
this.overrideMethod = overrideMethod;
}
@Override
@Nullable
protected String getBeanName() {
return StringUtils.hasText(this.beanName) ? this.beanName : super.getBeanName();
}
@Override
protected Object createOverride(String beanName, @Nullable BeanDefinition existingBeanDefinition,
@Nullable Object existingBeanInstance) {
try {
ReflectionUtils.makeAccessible(this.overrideMethod);
return this.overrideMethod.invoke(null);
}
catch (IllegalAccessException | InvocationTargetException ex) {
throw new IllegalStateException("Failed to invoke bean overriding method " + this.overrideMethod.getName() +
"; a static method with no formal parameters is expected", ex);
}
}
@Override
public boolean equals(@Nullable Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
if (!super.equals(o)) {
return false;
}
TestBeanOverrideMetadata that = (TestBeanOverrideMetadata) o;
return Objects.equals(this.overrideMethod, that.overrideMethod)
&& Objects.equals(this.beanName, that.beanName);
}
@Override
public int hashCode() {
return Objects.hash(super.hashCode(), this.overrideMethod, this.beanName);
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2024 the original author or authors.
* 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.
@@ -31,6 +31,7 @@ import org.springframework.core.ResolvableType;
import org.springframework.core.style.ToStringCreator;
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.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;
@@ -39,12 +40,12 @@ import org.springframework.util.StringUtils;
import static org.mockito.Mockito.mock;
/**
* A complete definition that can be used to create a Mockito mock.
* {@link OverrideMetadata} implementation for Mockito {@code mock} support.
*
* @author Phillip Webb
* @since 6.2
*/
class MockitoBeanMetadata extends MockitoMetadata {
class MockitoBeanOverrideMetadata extends MockitoOverrideMetadata {
private final Set<Class<?>> extraInterfaces;
@@ -53,12 +54,12 @@ class MockitoBeanMetadata extends MockitoMetadata {
private final boolean serializable;
MockitoBeanMetadata(MockitoBean annotation, Field field, ResolvableType typeToMock) {
MockitoBeanOverrideMetadata(MockitoBean annotation, Field field, ResolvableType typeToMock) {
this(annotation.name(), annotation.reset(), field, typeToMock,
annotation.extraInterfaces(), annotation.answers(), annotation.serializable());
}
MockitoBeanMetadata(String name, MockReset reset, Field field, ResolvableType typeToMock,
MockitoBeanOverrideMetadata(String name, MockReset reset, Field field, ResolvableType typeToMock,
Class<?>[] extraInterfaces, @Nullable Answers answer, boolean serializable) {
super(name, reset, false, field, typeToMock, BeanOverrideStrategy.REPLACE_OR_CREATE_DEFINITION);
@@ -68,18 +69,6 @@ class MockitoBeanMetadata extends MockitoMetadata {
this.serializable = serializable;
}
@Override
protected Object createOverride(String beanName, @Nullable BeanDefinition existingBeanDefinition, @Nullable Object existingBeanInstance) {
return createMock(beanName);
}
private Set<Class<?>> asClassSet(@Nullable Class<?>[] classes) {
Set<Class<?>> classSet = new LinkedHashSet<>();
if (classes != null) {
classSet.addAll(Arrays.asList(classes));
}
return Collections.unmodifiableSet(classSet);
}
/**
* Return the extra interfaces.
@@ -105,6 +94,19 @@ class MockitoBeanMetadata extends MockitoMetadata {
return this.serializable;
}
@Override
protected Object createOverride(String beanName, @Nullable BeanDefinition existingBeanDefinition, @Nullable Object existingBeanInstance) {
return createMock(beanName);
}
private Set<Class<?>> asClassSet(@Nullable Class<?>[] classes) {
Set<Class<?>> classSet = new LinkedHashSet<>();
if (classes != null) {
classSet.addAll(Arrays.asList(classes));
}
return Collections.unmodifiableSet(classSet);
}
@Override
public boolean equals(@Nullable Object obj) {
if (obj == this) {
@@ -113,7 +115,7 @@ class MockitoBeanMetadata extends MockitoMetadata {
if (obj == null || obj.getClass() != getClass()) {
return false;
}
MockitoBeanMetadata other = (MockitoBeanMetadata) obj;
MockitoBeanOverrideMetadata other = (MockitoBeanOverrideMetadata) obj;
boolean result = super.equals(obj);
result = result && ObjectUtils.nullSafeEquals(this.extraInterfaces, other.extraInterfaces);
result = result && ObjectUtils.nullSafeEquals(this.answer, other.answer);

View File

@@ -34,12 +34,12 @@ import org.springframework.test.context.bean.override.BeanOverrideProcessor;
class MockitoBeanOverrideProcessor implements BeanOverrideProcessor {
@Override
public MockitoMetadata createMetadata(Annotation overrideAnnotation, Class<?> testClass, Field field) {
public MockitoOverrideMetadata createMetadata(Annotation overrideAnnotation, Class<?> testClass, Field field) {
if (overrideAnnotation instanceof MockitoBean mockBean) {
return new MockitoBeanMetadata(mockBean, field, ResolvableType.forField(field, testClass));
return new MockitoBeanOverrideMetadata(mockBean, field, ResolvableType.forField(field, testClass));
}
else if (overrideAnnotation instanceof MockitoSpyBean spyBean) {
return new MockitoSpyBeanMetadata(spyBean, field, ResolvableType.forField(field, testClass));
return new MockitoSpyBeanOverrideMetadata(spyBean, field, ResolvableType.forField(field, testClass));
}
throw new IllegalStateException(String.format("Invalid annotation passed to MockitoBeanOverrideProcessor: "
+ "expected @MockitoBean/@MockitoSpyBean on field %s.%s",

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2024 the original author or authors.
* 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.
@@ -29,12 +29,12 @@ import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
/**
* Base class for Mockito override metadata.
* Base {@link OverrideMetadata} implementation for Mockito.
*
* @author Phillip Webb
* @since 6.2
*/
abstract class MockitoMetadata extends OverrideMetadata {
abstract class MockitoOverrideMetadata extends OverrideMetadata {
protected final String name;
@@ -43,7 +43,7 @@ abstract class MockitoMetadata extends OverrideMetadata {
private final boolean proxyTargetAware;
MockitoMetadata(String name, @Nullable MockReset reset, boolean proxyTargetAware, Field field,
MockitoOverrideMetadata(String name, @Nullable MockReset reset, boolean proxyTargetAware, Field field,
ResolvableType typeToOverride, BeanOverrideStrategy strategy) {
super(field, typeToOverride, strategy);
@@ -59,22 +59,6 @@ abstract class MockitoMetadata extends OverrideMetadata {
return StringUtils.hasText(this.name) ? this.name : super.getBeanName();
}
@Override
protected void track(Object mock, SingletonBeanRegistry trackingBeanRegistry) {
MockitoBeans tracker = null;
try {
tracker = (MockitoBeans) trackingBeanRegistry.getSingleton(MockitoBeans.class.getName());
}
catch (NoSuchBeanDefinitionException ignored) {
}
if (tracker == null) {
tracker= new MockitoBeans();
trackingBeanRegistry.registerSingleton(MockitoBeans.class.getName(), tracker);
}
tracker.add(mock);
}
/**
* Return the mock reset mode.
* @return the reset mode
@@ -91,6 +75,22 @@ abstract class MockitoMetadata extends OverrideMetadata {
return this.proxyTargetAware;
}
@Override
protected void track(Object mock, SingletonBeanRegistry trackingBeanRegistry) {
MockitoBeans tracker = null;
try {
tracker = (MockitoBeans) trackingBeanRegistry.getSingleton(MockitoBeans.class.getName());
}
catch (NoSuchBeanDefinitionException ignored) {
}
if (tracker == null) {
tracker= new MockitoBeans();
trackingBeanRegistry.registerSingleton(MockitoBeans.class.getName(), tracker);
}
tracker.add(mock);
}
@Override
public boolean equals(@Nullable Object obj) {
if (obj == this) {
@@ -99,7 +99,7 @@ abstract class MockitoMetadata extends OverrideMetadata {
if (obj == null || !getClass().isAssignableFrom(obj.getClass())) {
return false;
}
MockitoMetadata other = (MockitoMetadata) obj;
MockitoOverrideMetadata other = (MockitoOverrideMetadata) obj;
boolean result = super.equals(obj);
result = result && ObjectUtils.nullSafeEquals(this.name, other.name);
result = result && ObjectUtils.nullSafeEquals(this.reset, other.reset);

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2024 the original author or authors.
* 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.
@@ -40,20 +40,20 @@ import org.springframework.util.StringUtils;
import static org.mockito.Mockito.mock;
/**
* {@link OverrideMetadata} for Mockito {@code spy} support.
* {@link OverrideMetadata} implementation for Mockito {@code spy} support.
*
* @author Phillip Webb
* @author Simon Baslé
* @since 6.2
*/
class MockitoSpyBeanMetadata extends MockitoMetadata {
class MockitoSpyBeanOverrideMetadata extends MockitoOverrideMetadata {
MockitoSpyBeanMetadata(MockitoSpyBean spyAnnotation, Field field, ResolvableType typeToSpy) {
MockitoSpyBeanOverrideMetadata(MockitoSpyBean spyAnnotation, Field field, ResolvableType typeToSpy) {
this(spyAnnotation.name(), spyAnnotation.reset(), spyAnnotation.proxyTargetAware(),
field, typeToSpy);
}
MockitoSpyBeanMetadata(String name, MockReset reset, boolean proxyTargetAware, Field field, ResolvableType typeToSpy) {
MockitoSpyBeanOverrideMetadata(String name, MockReset reset, boolean proxyTargetAware, Field field, ResolvableType typeToSpy) {
super(name, reset, proxyTargetAware, field, typeToSpy, BeanOverrideStrategy.WRAP_BEAN);
Assert.notNull(typeToSpy, "typeToSpy must not be null");
}
@@ -68,33 +68,6 @@ class MockitoSpyBeanMetadata extends MockitoMetadata {
return createSpy(beanName, existingBeanInstance);
}
@Override
public boolean equals(@Nullable Object obj) {
if (obj == this) {
return true;
}
// For SpyBean we want the class to be exactly the same.
if (obj == null || obj.getClass() != getClass()) {
return false;
}
MockitoSpyBeanMetadata that = (MockitoSpyBeanMetadata) obj;
return (super.equals(obj) && ObjectUtils.nullSafeEquals(getBeanType(), that.getBeanType()));
}
@Override
public int hashCode() {
return Objects.hash(super.hashCode(), getBeanType());
}
@Override
public String toString() {
return new ToStringCreator(this)
.append("beanName", getBeanName())
.append("beanType", getBeanType())
.append("reset", getReset())
.toString();
}
@SuppressWarnings("unchecked")
<T> T createSpy(String name, Object instance) {
Assert.notNull(instance, "Instance must not be null");
@@ -124,6 +97,34 @@ class MockitoSpyBeanMetadata extends MockitoMetadata {
return (T) mock(toSpy, settings);
}
@Override
public boolean equals(@Nullable Object obj) {
if (obj == this) {
return true;
}
// For SpyBean we want the class to be exactly the same.
if (obj == null || obj.getClass() != getClass()) {
return false;
}
MockitoSpyBeanOverrideMetadata that = (MockitoSpyBeanOverrideMetadata) obj;
return (super.equals(obj) && ObjectUtils.nullSafeEquals(getBeanType(), that.getBeanType()));
}
@Override
public int hashCode() {
return Objects.hash(super.hashCode(), getBeanType());
}
@Override
public String toString() {
return new ToStringCreator(this)
.append("beanName", getBeanName())
.append("beanType", getBeanType())
.append("reset", getReset())
.toString();
}
/**
* A {@link VerificationStartedListener} that bypasses any proxy created by
* Spring AOP when the verification of a spy starts.
@@ -134,7 +135,6 @@ class MockitoSpyBeanMetadata extends MockitoMetadata {
public void onVerificationStarted(VerificationStartedEvent event) {
event.setMock(AopTestUtils.getUltimateTargetObject(event.getMock()));
}
}
}