Simplify and document BeanOverrideTestExecutionListener

This commit simplifies the implementation of
BeanOverrideTestExecutionListener by introducing a static
injectFields() utility method and removing the use of BiConsumers,
records, and duplicated code.

This commit also introduces Javadoc for all methods in
BeanOverrideTestExecutionListener.

Closes gh-33660
This commit is contained in:
Sam Brannen 2024-10-05 15:18:57 +02:00
parent b0c7d15d9f
commit 28b9cf3b83
1 changed files with 42 additions and 48 deletions

View File

@ -18,7 +18,6 @@ package org.springframework.test.context.bean.override;
import java.lang.reflect.Field;
import java.util.List;
import java.util.function.BiConsumer;
import org.springframework.test.context.TestContext;
import org.springframework.test.context.support.AbstractTestExecutionListener;
@ -26,10 +25,12 @@ import org.springframework.test.context.support.DependencyInjectionTestExecution
import org.springframework.util.ReflectionUtils;
/**
* {@code TestExecutionListener} that enables Bean Override support in tests,
* injecting overridden beans in appropriate fields of the test instance.
* {@code TestExecutionListener} that enables {@link BeanOverride @BeanOverride}
* support in tests, by injecting overridden beans in appropriate fields of the
* test instance.
*
* @author Simon Baslé
* @author Sam Brannen
* @since 6.2
*/
public class BeanOverrideTestExecutionListener extends AbstractTestExecutionListener {
@ -42,64 +43,57 @@ public class BeanOverrideTestExecutionListener extends AbstractTestExecutionList
return LOWEST_PRECEDENCE - 50;
}
/**
* Inject each {@link BeanOverride @BeanOverride} field in the
* {@linkplain Object test instance} of the supplied {@linkplain TestContext
* test context} with a corresponding bean override instance.
*/
@Override
public void prepareTestInstance(TestContext testContext) throws Exception {
injectFields(testContext);
injectFields(testContext, false);
}
/**
* Re-inject each {@link BeanOverride @BeanOverride} field in the
* {@linkplain Object test instance} of the supplied {@linkplain TestContext
* test context} with a corresponding bean override instance.
* <p>This method does nothing if the
* {@link DependencyInjectionTestExecutionListener#REINJECT_DEPENDENCIES_ATTRIBUTE
* REINJECT_DEPENDENCIES_ATTRIBUTE} attribute is not present in the
* {@code TestContext} with a value of {@link Boolean#TRUE}.
*/
@Override
public void beforeTestMethod(TestContext testContext) throws Exception {
reinjectFieldsIfNecessary(testContext);
}
/**
* Process the test instance and make sure that fields flagged for bean
* overriding are injected with the overridden bean instance.
*/
protected void injectFields(TestContext testContext) {
postProcessFields(testContext, (testMetadata, registrar) ->
registrar.inject(testMetadata.testInstance, testMetadata.overrideMetadata));
}
/**
* Process the test instance and make sure that fields flagged for bean
* overriding are injected with the overridden bean instance, if necessary.
* <p>If a fresh instance is required, the field is nulled out and then
* re-injected with the overridden bean instance.
* <p>This method does nothing if the
* {@link DependencyInjectionTestExecutionListener#REINJECT_DEPENDENCIES_ATTRIBUTE}
* attribute is not present in the {@code TestContext} with a value of {@link Boolean#TRUE}.
*/
protected void reinjectFieldsIfNecessary(TestContext testContext) throws Exception {
if (Boolean.TRUE.equals(
testContext.getAttribute(DependencyInjectionTestExecutionListener.REINJECT_DEPENDENCIES_ATTRIBUTE))) {
postProcessFields(testContext, (testMetadata, registrar) -> {
Object testInstance = testMetadata.testInstance;
Field field = testMetadata.overrideMetadata.getField();
ReflectionUtils.makeAccessible(field);
ReflectionUtils.setField(field, testInstance, null);
registrar.inject(testInstance, testMetadata.overrideMetadata);
});
Object reinjectDependenciesAttribute = testContext.getAttribute(
DependencyInjectionTestExecutionListener.REINJECT_DEPENDENCIES_ATTRIBUTE);
if (Boolean.TRUE.equals(reinjectDependenciesAttribute)) {
injectFields(testContext, true);
}
}
private void postProcessFields(TestContext testContext, BiConsumer<TestContextOverrideMetadata,
BeanOverrideRegistrar> consumer) {
Class<?> testClass = testContext.getTestClass();
Object testInstance = testContext.getTestInstance();
List<OverrideMetadata> metadataForFields = OverrideMetadata.forTestClass(testClass);
if (!metadataForFields.isEmpty()) {
/**
* Inject each {@link BeanOverride @BeanOverride} field in the test instance with
* a corresponding bean override instance.
* <p>If the {@code reinjectFields} flag is {@code true} (which indicates that
* a fresh instance is required), the field is nulled out before injecting
* the overridden bean instance.
*/
private static void injectFields(TestContext testContext, boolean reinjectFields) {
List<OverrideMetadata> overrideMetadataList = OverrideMetadata.forTestClass(testContext.getTestClass());
if (!overrideMetadataList.isEmpty()) {
Object testInstance = testContext.getTestInstance();
BeanOverrideRegistrar registrar = testContext.getApplicationContext()
.getBean(BeanOverrideContextCustomizer.REGISTRAR_BEAN_NAME, BeanOverrideRegistrar.class);
for (OverrideMetadata metadata : metadataForFields) {
consumer.accept(new TestContextOverrideMetadata(testInstance, metadata), registrar);
for (OverrideMetadata overrideMetadata : overrideMetadataList) {
if (reinjectFields) {
Field field = overrideMetadata.getField();
ReflectionUtils.makeAccessible(field);
ReflectionUtils.setField(field, testInstance, null);
}
registrar.inject(testInstance, overrideMetadata);
}
}
}
private record TestContextOverrideMetadata(Object testInstance, OverrideMetadata overrideMetadata) {}
}