Polish bean override internals

This commit is contained in:
Sam Brannen 2024-09-18 10:09:22 +02:00
parent c832d5f496
commit 6c2cba5d8a
6 changed files with 54 additions and 78 deletions

View File

@ -35,7 +35,6 @@ import org.springframework.test.context.bean.override.BeanOverrideStrategy;
import org.springframework.test.context.bean.override.OverrideMetadata; import org.springframework.test.context.bean.override.OverrideMetadata;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.ClassUtils; import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
/** /**
@ -43,13 +42,14 @@ import org.springframework.util.StringUtils;
* *
* @author Phillip Webb * @author Phillip Webb
* @author Stephane Nicoll * @author Stephane Nicoll
* @author Sam Brannen
* @since 6.2 * @since 6.2
*/ */
class MockitoBeanOverrideMetadata extends MockitoOverrideMetadata { class MockitoBeanOverrideMetadata extends MockitoOverrideMetadata {
private final Set<Class<?>> extraInterfaces; private final Set<Class<?>> extraInterfaces;
private final Answers answer; private final Answers answers;
private final boolean serializable; private final boolean serializable;
@ -59,21 +59,22 @@ class MockitoBeanOverrideMetadata extends MockitoOverrideMetadata {
annotation.reset(), annotation.extraInterfaces(), annotation.answers(), annotation.serializable()); annotation.reset(), annotation.extraInterfaces(), annotation.answers(), annotation.serializable());
} }
MockitoBeanOverrideMetadata(Field field, ResolvableType typeToMock, @Nullable String beanName, MockReset reset, private MockitoBeanOverrideMetadata(Field field, ResolvableType typeToMock, @Nullable String beanName, MockReset reset,
Class<?>[] extraInterfaces, @Nullable Answers answer, boolean serializable) { Class<?>[] extraInterfaces, @Nullable Answers answers, boolean serializable) {
super(field, typeToMock, beanName, BeanOverrideStrategy.REPLACE_OR_CREATE_DEFINITION, reset, false); super(field, typeToMock, beanName, BeanOverrideStrategy.REPLACE_OR_CREATE_DEFINITION, reset, false);
Assert.notNull(typeToMock, "'typeToMock' must not be null"); Assert.notNull(typeToMock, "'typeToMock' must not be null");
this.extraInterfaces = asClassSet(extraInterfaces); this.extraInterfaces = asClassSet(extraInterfaces);
this.answer = (answer != null) ? answer : Answers.RETURNS_DEFAULTS; this.answers = (answers != null ? answers : Answers.RETURNS_DEFAULTS);
this.serializable = serializable; this.serializable = serializable;
} }
private static Set<Class<?>> asClassSet(@Nullable Class<?>[] classes) {
Set<Class<?>> classSet = new LinkedHashSet<>(); private static Set<Class<?>> asClassSet(Class<?>[] classes) {
if (classes != null) { if (classes.length == 0) {
classSet.addAll(Arrays.asList(classes)); return Collections.emptySet();
} }
Set<Class<?>> classSet = new LinkedHashSet<>(Arrays.asList(classes));
return Collections.unmodifiableSet(classSet); return Collections.unmodifiableSet(classSet);
} }
@ -90,12 +91,12 @@ class MockitoBeanOverrideMetadata extends MockitoOverrideMetadata {
* Return the {@link Answers}. * Return the {@link Answers}.
* @return the answers mode * @return the answers mode
*/ */
Answers getAnswer() { Answers getAnswers() {
return this.answer; return this.answers;
} }
/** /**
* Return if the mock is serializable. * Determine if the mock is serializable.
* @return {@code true} if the mock is serializable * @return {@code true} if the mock is serializable
*/ */
boolean isSerializable() { boolean isSerializable() {
@ -108,7 +109,7 @@ class MockitoBeanOverrideMetadata extends MockitoOverrideMetadata {
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
<T> T createMock(String name) { private <T> T createMock(String name) {
MockSettings settings = MockReset.withSettings(getReset()); MockSettings settings = MockReset.withSettings(getReset());
if (StringUtils.hasLength(name)) { if (StringUtils.hasLength(name)) {
settings.name(name); settings.name(name);
@ -116,7 +117,7 @@ class MockitoBeanOverrideMetadata extends MockitoOverrideMetadata {
if (!this.extraInterfaces.isEmpty()) { if (!this.extraInterfaces.isEmpty()) {
settings.extraInterfaces(ClassUtils.toClassArray(this.extraInterfaces)); settings.extraInterfaces(ClassUtils.toClassArray(this.extraInterfaces));
} }
settings.defaultAnswer(this.answer); settings.defaultAnswer(this.answers);
if (this.serializable) { if (this.serializable) {
settings.serializable(); settings.serializable();
} }
@ -132,27 +133,26 @@ class MockitoBeanOverrideMetadata extends MockitoOverrideMetadata {
if (other == null || other.getClass() != getClass()) { if (other == null || other.getClass() != getClass()) {
return false; return false;
} }
MockitoBeanOverrideMetadata that = (MockitoBeanOverrideMetadata) other; return (other instanceof MockitoBeanOverrideMetadata that && super.equals(that) &&
boolean result = super.equals(that); (this.serializable == that.serializable) && (this.answers == that.answers) &&
result = result && ObjectUtils.nullSafeEquals(this.extraInterfaces, that.extraInterfaces); Objects.equals(this.extraInterfaces, that.extraInterfaces));
result = result && ObjectUtils.nullSafeEquals(this.answer, that.answer);
result = result && this.serializable == that.serializable;
return result;
} }
@Override @Override
public int hashCode() { public int hashCode() {
return Objects.hash(this.extraInterfaces, this.answer, this.serializable) + super.hashCode(); return super.hashCode() + Objects.hash(this.extraInterfaces, this.answers, this.serializable);
} }
@Override @Override
public String toString() { public String toString() {
return new ToStringCreator(this) return new ToStringCreator(this)
.append("field", getField())
.append("beanType", getBeanType()) .append("beanType", getBeanType())
.append("beanName", getBeanName()) .append("beanName", getBeanName())
.append("strategy", getStrategy())
.append("reset", getReset()) .append("reset", getReset())
.append("extraInterfaces", getExtraInterfaces()) .append("extraInterfaces", getExtraInterfaces())
.append("answer", getAnswer()) .append("answers", getAnswers())
.append("serializable", isSerializable()) .append("serializable", isSerializable())
.toString(); .toString();
} }

View File

@ -17,16 +17,18 @@
package org.springframework.test.context.bean.override.mockito; package org.springframework.test.context.bean.override.mockito;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Iterator;
import java.util.List; import java.util.List;
import org.mockito.Mockito;
/** /**
* Beans created using Mockito. * Beans created using Mockito.
* *
* @author Andy Wilkinson * @author Andy Wilkinson
* @author Sam Brannen
* @since 6.2 * @since 6.2
*/ */
class MockitoBeans implements Iterable<Object> { class MockitoBeans {
private final List<Object> beans = new ArrayList<>(); private final List<Object> beans = new ArrayList<>();
@ -35,9 +37,8 @@ class MockitoBeans implements Iterable<Object> {
this.beans.add(bean); this.beans.add(bean);
} }
@Override void resetAll() {
public Iterator<Object> iterator() { this.beans.forEach(Mockito::reset);
return this.beans.iterator();
} }
} }

View File

@ -22,16 +22,17 @@ import java.util.Objects;
import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.config.SingletonBeanRegistry; import org.springframework.beans.factory.config.SingletonBeanRegistry;
import org.springframework.core.ResolvableType; import org.springframework.core.ResolvableType;
import org.springframework.core.style.ToStringCreator;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
import org.springframework.test.context.bean.override.BeanOverrideStrategy; import org.springframework.test.context.bean.override.BeanOverrideStrategy;
import org.springframework.test.context.bean.override.OverrideMetadata; import org.springframework.test.context.bean.override.OverrideMetadata;
import org.springframework.util.ObjectUtils;
/** /**
* Base {@link OverrideMetadata} implementation for Mockito. * Base {@link OverrideMetadata} implementation for Mockito.
* *
* @author Phillip Webb * @author Phillip Webb
* @author Stephane Nicoll * @author Stephane Nicoll
* @author Sam Brannen
* @since 6.2 * @since 6.2
*/ */
abstract class MockitoOverrideMetadata extends OverrideMetadata { abstract class MockitoOverrideMetadata extends OverrideMetadata {
@ -45,7 +46,7 @@ abstract class MockitoOverrideMetadata extends OverrideMetadata {
BeanOverrideStrategy strategy, @Nullable MockReset reset, boolean proxyTargetAware) { BeanOverrideStrategy strategy, @Nullable MockReset reset, boolean proxyTargetAware) {
super(field, beanType, beanName, strategy); super(field, beanType, beanName, strategy);
this.reset = (reset != null) ? reset : MockReset.AFTER; this.reset = (reset != null ? reset : MockReset.AFTER);
this.proxyTargetAware = proxyTargetAware; this.proxyTargetAware = proxyTargetAware;
} }
@ -73,10 +74,9 @@ abstract class MockitoOverrideMetadata extends OverrideMetadata {
tracker = (MockitoBeans) trackingBeanRegistry.getSingleton(MockitoBeans.class.getName()); tracker = (MockitoBeans) trackingBeanRegistry.getSingleton(MockitoBeans.class.getName());
} }
catch (NoSuchBeanDefinitionException ignored) { catch (NoSuchBeanDefinitionException ignored) {
} }
if (tracker == null) { if (tracker == null) {
tracker= new MockitoBeans(); tracker = new MockitoBeans();
trackingBeanRegistry.registerSingleton(MockitoBeans.class.getName(), tracker); trackingBeanRegistry.registerSingleton(MockitoBeans.class.getName(), tracker);
} }
tracker.add(mock); tracker.add(mock);
@ -87,19 +87,25 @@ abstract class MockitoOverrideMetadata extends OverrideMetadata {
if (other == this) { if (other == this) {
return true; return true;
} }
if (other == null || !getClass().isAssignableFrom(other.getClass())) { return (other instanceof MockitoOverrideMetadata that && super.equals(that) &&
return false; (this.reset == that.reset) && (this.proxyTargetAware == that.proxyTargetAware));
}
MockitoOverrideMetadata that = (MockitoOverrideMetadata) other;
boolean result = super.equals(that);
result = result && ObjectUtils.nullSafeEquals(this.reset, that.reset);
result = result && ObjectUtils.nullSafeEquals(this.proxyTargetAware, that.proxyTargetAware);
return result;
} }
@Override @Override
public int hashCode() { public int hashCode() {
return Objects.hash(this.reset, this.proxyTargetAware) + super.hashCode(); return super.hashCode() + Objects.hash(this.reset, this.proxyTargetAware);
}
@Override
public String toString() {
return new ToStringCreator(this)
.append("field", getField())
.append("beanType", getBeanType())
.append("beanName", getBeanName())
.append("strategy", getStrategy())
.append("reset", getReset())
.append("proxyTargetAware", isProxyTargetAware())
.toString();
} }
} }

View File

@ -87,12 +87,7 @@ public class MockitoResetTestExecutionListener extends AbstractTestExecutionList
} }
} }
try { try {
MockitoBeans mockedBeans = beanFactory.getBean(MockitoBeans.class); beanFactory.getBean(MockitoBeans.class).resetAll();
for (Object mockedBean : mockedBeans) {
if (reset.equals(MockReset.get(mockedBean))) {
Mockito.reset(mockedBean);
}
}
} }
catch (NoSuchBeanDefinitionException ex) { catch (NoSuchBeanDefinitionException ex) {
// Continue // Continue

View File

@ -27,7 +27,6 @@ import org.mockito.listeners.VerificationStartedListener;
import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.core.ResolvableType; import org.springframework.core.ResolvableType;
import org.springframework.core.style.ToStringCreator;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
import org.springframework.test.context.bean.override.BeanOverrideStrategy; import org.springframework.test.context.bean.override.BeanOverrideStrategy;
import org.springframework.test.context.bean.override.OverrideMetadata; import org.springframework.test.context.bean.override.OverrideMetadata;
@ -68,8 +67,7 @@ class MockitoSpyBeanOverrideMetadata extends MockitoOverrideMetadata {
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
<T> T createSpy(String name, Object instance) { private <T> T createSpy(String name, Object instance) {
Assert.notNull(instance, "Instance must not be null");
Class<?> resolvedTypeToOverride = getBeanType().resolve(); Class<?> resolvedTypeToOverride = getBeanType().resolve();
Assert.notNull(resolvedTypeToOverride, "Failed to resolve type to override"); Assert.notNull(resolvedTypeToOverride, "Failed to resolve type to override");
Assert.isInstanceOf(resolvedTypeToOverride, instance); Assert.isInstanceOf(resolvedTypeToOverride, instance);
@ -96,15 +94,6 @@ class MockitoSpyBeanOverrideMetadata extends MockitoOverrideMetadata {
return (T) Mockito.mock(toSpy, settings); return (T) Mockito.mock(toSpy, settings);
} }
@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 * A {@link VerificationStartedListener} that bypasses any proxy created by

View File

@ -56,22 +56,14 @@ public class MockitoBeanForByNameLookupIntegrationTests {
@Test @Test
void fieldHasOverride(ApplicationContext ctx) { void fieldAndRenamedFieldHaveSameOverride(ApplicationContext ctx) {
assertThat(ctx.getBean("field"))
.isInstanceOf(ExampleService.class)
.satisfies(o -> assertThat(Mockito.mockingDetails(o).isMock()).as("isMock").isTrue())
.isSameAs(this.field);
assertThat(this.field.greeting()).as("mocked greeting").isNull();
}
@Test
void renamedFieldHasOverride(ApplicationContext ctx) {
assertThat(ctx.getBean("field")) assertThat(ctx.getBean("field"))
.isInstanceOf(ExampleService.class) .isInstanceOf(ExampleService.class)
.satisfies(o -> assertThat(Mockito.mockingDetails(o).isMock()).as("isMock").isTrue()) .satisfies(o -> assertThat(Mockito.mockingDetails(o).isMock()).as("isMock").isTrue())
.isSameAs(this.field)
.isSameAs(this.renamed1); .isSameAs(this.renamed1);
assertThat(this.field.greeting()).as("mocked greeting").isNull();
assertThat(this.renamed1.greeting()).as("mocked greeting").isNull(); assertThat(this.renamed1.greeting()).as("mocked greeting").isNull();
} }
@ -91,18 +83,11 @@ public class MockitoBeanForByNameLookupIntegrationTests {
public class MockitoBeanNestedTests { public class MockitoBeanNestedTests {
@Test @Test
void fieldHasOverride(ApplicationContext ctx) { void fieldAndRenamedFieldHaveSameOverride(ApplicationContext ctx) {
assertThat(ctx.getBean("nestedField"))
.isInstanceOf(ExampleService.class)
.satisfies(o -> assertThat(Mockito.mockingDetails(o).isMock()).as("isMock").isTrue())
.isSameAs(nestedField);
}
@Test
void renamedFieldHasOverride(ApplicationContext ctx) {
assertThat(ctx.getBean("nestedField")) assertThat(ctx.getBean("nestedField"))
.isInstanceOf(ExampleService.class) .isInstanceOf(ExampleService.class)
.satisfies(o -> assertThat(Mockito.mockingDetails(o).isMock()).as("isMock").isTrue()) .satisfies(o -> assertThat(Mockito.mockingDetails(o).isMock()).as("isMock").isTrue())
.isSameAs(nestedField)
.isSameAs(renamed2); .isSameAs(renamed2);
} }