Skip to content

Commit

Permalink
Simplify and document BeanOverrideTestExecutionListener
Browse files Browse the repository at this point in the history
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
  • Loading branch information
sbrannen committed Oct 6, 2024
1 parent b0c7d15 commit 28b9cf3
Showing 1 changed file with 41 additions and 47 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,19 @@

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;
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
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 {
Expand All @@ -42,64 +43,57 @@ public int getOrder() {
return LOWEST_PRECEDENCE - 50;
}

@Override
public void prepareTestInstance(TestContext testContext) throws Exception {
injectFields(testContext);
}

@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.
* 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.
*/
protected void injectFields(TestContext testContext) {
postProcessFields(testContext, (testMetadata, registrar) ->
registrar.inject(testMetadata.testInstance, testMetadata.overrideMetadata));
@Override
public void prepareTestInstance(TestContext testContext) throws Exception {
injectFields(testContext, false);
}

/**
* 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.
* 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}
* attribute is not present in the {@code TestContext} with a value of {@link Boolean#TRUE}.
* {@link DependencyInjectionTestExecutionListener#REINJECT_DEPENDENCIES_ATTRIBUTE
* 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);
});
@Override
public void beforeTestMethod(TestContext testContext) throws Exception {
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) {}

}

0 comments on commit 28b9cf3

Please sign in to comment.