Skip to content

Commit

Permalink
GH-360 - Fix potential UnsupportedOperationException in test bootstrap.
Browse files Browse the repository at this point in the history
See GH-345 and GH-356 for details.
  • Loading branch information
odrotbohm committed Nov 3, 2023
1 parent b501554 commit a7b4f77
Show file tree
Hide file tree
Showing 2 changed files with 54 additions and 37 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,23 +15,20 @@
*/
package org.springframework.modulith.test;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.config.ConstructorArgumentValues.ValueHolder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.boot.autoconfigure.AutoConfigureOrder;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.Ordered;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;

/**
Expand Down Expand Up @@ -66,49 +63,25 @@ public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, B
LOGGER.info("Re-configuring auto-configuration and entity scan packages to: {}.",
StringUtils.collectionToDelimitedString(basePackages, ", "));

setBasePackagesOn(registry, AUTOCONFIG_PACKAGES, "BasePackagesBeanDefinition", "basePackages", basePackages);
setBasePackagesOn(registry, ENTITY_SCAN_PACKAGE, "EntityScanPackagesBeanDefinition", "packageNames",
basePackages);
setBasePackagesOn(registry, AUTOCONFIG_PACKAGES, basePackages);
setBasePackagesOn(registry, ENTITY_SCAN_PACKAGE, basePackages);
}

@SuppressWarnings("unchecked")
private void setBasePackagesOn(BeanDefinitionRegistry registry, String beanName, String definitionType,
String fieldName, List<String> packages) {
private void setBasePackagesOn(BeanDefinitionRegistry registry, String beanName, List<String> packages) {

if (!registry.containsBeanDefinition(beanName)) {
return;
}

var packagesToSet = new ArrayList<>(packages);
var definition = registry.getBeanDefinition(beanName);
var holder = definition.getConstructorArgumentValues().getArgumentValue(0, String[].class);

// For Boot 2.4, we deal with a BasePackagesBeanDefinition
var field = Arrays.stream(definition.getClass().getDeclaredFields())
.filter(__ -> definition.getClass().getSimpleName().equals(definitionType))
.filter(it -> it.getName().equals(fieldName))
.findFirst()
.orElse(null);
Arrays.stream((String[]) holder.getValue())
.filter(it -> it.startsWith("org.springframework.modulith"))
.forEach(packagesToSet::add);

if (field != null) {

// Keep all auto-configuration packages from Moduliths

ReflectionUtils.makeAccessible(field);
((Set<String>) ReflectionUtils.getField(field, definition)).stream()
.filter(it -> it.startsWith("org.springframework.modulith"))
.forEach(packages::add);

ReflectionUtils.setField(field, definition, new HashSet<>(packages));

} else {

ValueHolder holder = definition.getConstructorArgumentValues().getArgumentValue(0, String[].class);
Arrays.stream((String[]) holder.getValue())
.filter(it -> it.startsWith("org.springframework.modulith"))
.forEach(packages::add);

// Fall back to customize the bean definition in a Boot 2.3 arrangement
definition.getConstructorArgumentValues().addIndexedArgumentValue(0, packages.toArray(String[]::new));
}
definition.getConstructorArgumentValues().addIndexedArgumentValue(0, packagesToSet.toArray(String[]::new));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package org.springframework.modulith.test;

import static org.assertj.core.api.Assertions.*;
import static org.mockito.Mockito.*;

import java.util.stream.Stream;

import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.boot.autoconfigure.domain.EntityScanPackages;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;

/**
* Integration tests for {@link ModuleTestAutoConfiguration}.
*
* @author Oliver Drotbohm
*/
class ModuleTestAutoConfigurationIntegrationTests {

@Test // GH-360
void bootstrapsTestWithManualEntityScanOfModulithPackage() {

var execution = mock(ModuleTestExecution.class);

doReturn(Stream.of("org.springframework.modulith.test"))
.when(execution).getBasePackages();

new ApplicationContextRunner()
.withBean(ModuleTestExecution.class, () -> execution)
.withConfiguration(AutoConfigurations.of(ModuleTestAutoConfiguration.class))
.withUserConfiguration(ManualModulithEntityScan.class)
.run(ctx -> {

assertThat(ctx).hasNotFailed();
assertThat(ctx.getBean(EntityScanPackages.class).getPackageNames())
.containsExactlyInAnyOrder("org.springframework.modulith.test",
"org.springframework.modulith.events.jpa");
});
}

@EntityScan("org.springframework.modulith.events.jpa")
static class ManualModulithEntityScan {}
}

0 comments on commit a7b4f77

Please sign in to comment.