Skip to content

Commit e22ea0d

Browse files
committed
GH-1494 - Include bean definitions from @TestConfiguration classes in test run.
1 parent 5b7167a commit e22ea0d

File tree

5 files changed

+118
-32
lines changed

5 files changed

+118
-32
lines changed
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/*
2+
* Copyright 2018-2025 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.acme.myproject.moduleE;
17+
18+
import org.springframework.stereotype.Component;
19+
20+
/**
21+
* @author Oliver Drotbohm
22+
*/
23+
@Component
24+
public class AnotherServiceComponentE {}

spring-modulith-integration-test/src/test/java/com/acme/myproject/moduleD/ModuleDTest.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,15 @@
1818
import static org.assertj.core.api.Assertions.*;
1919

2020
import org.junit.jupiter.api.Test;
21+
import org.mockito.Mockito;
22+
import org.mockito.internal.util.MockUtil;
2123
import org.springframework.beans.factory.annotation.Autowired;
24+
import org.springframework.boot.test.context.TestConfiguration;
2225
import org.springframework.context.ConfigurableApplicationContext;
26+
import org.springframework.context.annotation.Bean;
2327

2428
import com.acme.myproject.NonVerifyingModuleTest;
29+
import com.acme.myproject.moduleE.AnotherServiceComponentE;
2530
import com.acme.myproject.moduleE.ServiceComponentE;
2631

2732
/**
@@ -36,4 +41,21 @@ public class ModuleDTest {
3641
void dropsManuallyDeclaredBeanOfNonIncludedModule() {
3742
assertThat(context.getBeanNamesForType(ServiceComponentE.class)).isEmpty();
3843
}
44+
45+
@Test // GH-1494
46+
void considersBeanFromTestConfiguration() {
47+
48+
assertThat(context.getBean(AnotherServiceComponentE.class))
49+
.extracting(MockUtil::isMock)
50+
.isEqualTo(true);
51+
}
52+
53+
@TestConfiguration
54+
static class TestConfig {
55+
56+
@Bean
57+
AnotherServiceComponentE mock() {
58+
return Mockito.mock(AnotherServiceComponentE.class);
59+
}
60+
}
3961
}

spring-modulith-test/src/main/java/org/springframework/modulith/test/ModuleContextCustomizerFactory.java

Lines changed: 63 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@
1515
*/
1616
package org.springframework.modulith.test;
1717

18+
import java.util.ArrayList;
1819
import java.util.Arrays;
20+
import java.util.Collection;
1921
import java.util.List;
2022
import java.util.Objects;
2123
import java.util.function.Predicate;
@@ -29,10 +31,12 @@
2931
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
3032
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
3133
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
34+
import org.springframework.boot.test.context.TestConfiguration;
3235
import org.springframework.context.ConfigurableApplicationContext;
36+
import org.springframework.core.annotation.AnnotatedElementUtils;
3337
import org.springframework.modulith.core.ApplicationModule;
3438
import org.springframework.modulith.core.ApplicationModuleIdentifier;
35-
import org.springframework.modulith.core.JavaPackage;
39+
import org.springframework.modulith.core.ApplicationModules;
3640
import org.springframework.test.context.ContextConfigurationAttributes;
3741
import org.springframework.test.context.ContextCustomizer;
3842
import org.springframework.test.context.ContextCustomizerFactory;
@@ -238,38 +242,80 @@ public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) t
238242

239243
for (String name : registry.getBeanDefinitionNames()) {
240244

241-
var type = factory.getType(name, false);
242-
var module = modules.getModuleByType(type)
243-
.filter(Predicate.not(ApplicationModule::isRootModule));
244-
245-
// Not a module type -> pass
246-
if (module.isEmpty()) {
245+
if (include(name, modules, factory)) {
247246
continue;
248247
}
249248

250-
var packagesIncludedInTestRun = execution.getBasePackages().toList();
249+
var type = factory.getType(name, false);
251250

252-
// A type of a module bootstrapped -> pass
253-
if (module.map(ApplicationModule::getBasePackage)
254-
.map(JavaPackage::getName)
255-
.filter(packagesIncludedInTestRun::contains).isPresent()) {
256-
continue;
251+
if (type != null) {
252+
LOGGER.trace(
253+
"Dropping bean definition {} for type {} as it is not included in an application module to be bootstrapped!",
254+
name, type.getName());
257255
}
258256

259-
LOGGER.trace(
260-
"Dropping bean definition {} for type {} as it is not included in an application module to be bootstrapped!",
261-
name, type.getName());
262-
263257
// Remove bean definition from bootstrap
264258
registry.removeBeanDefinition(name);
265259
}
266260
}
267261

262+
private boolean include(String beanDefinitionName, ApplicationModules modules,
263+
ConfigurableListableBeanFactory factory) {
264+
265+
var types = getTypeOrTestConfigurationFactoryBean(beanDefinitionName, factory);
266+
267+
var result = modules.stream()
268+
.filter(Predicate.not(ApplicationModule::isRootModule))
269+
.filter(it -> types.stream().anyMatch(type -> it.couldContain(type)))
270+
.map(ApplicationModule::getBasePackage)
271+
.map(execution.getBasePackages()::contains)
272+
.toList();
273+
274+
return result.isEmpty() || result.contains(true);
275+
}
276+
268277
/*
269278
* (non-Javadoc)
270279
* @see org.springframework.beans.factory.config.BeanFactoryPostProcessor#postProcessBeanFactory(org.springframework.beans.factory.config.ConfigurableListableBeanFactory)
271280
*/
272281
@Override
273282
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {}
283+
284+
/**
285+
* Returns the type of the {@link org.springframework.beans.factory.config.BeanDefinition} of the given name and
286+
* optionally its factory bean's type if annotated with {@link TestConfiguration}.
287+
*
288+
* @param beanDefinitionName must not be {@literal null} or empty.
289+
* @param factory must not be {@literal null}.
290+
* @return will never be {@literal null}.
291+
*/
292+
private static Collection<Class<?>> getTypeOrTestConfigurationFactoryBean(String beanDefinitionName,
293+
ConfigurableListableBeanFactory factory) {
294+
295+
var result = new ArrayList<Class<?>>();
296+
var type = factory.getType(beanDefinitionName, false);
297+
298+
if (type != null) {
299+
result.add(type);
300+
}
301+
302+
var factoryName = factory.getBeanDefinition(beanDefinitionName).getFactoryBeanName();
303+
304+
if (factoryName == null) {
305+
return result;
306+
}
307+
308+
var factoryType = factory.getType(factoryName, false);
309+
310+
if (factoryType == null) {
311+
return result;
312+
}
313+
314+
if (AnnotatedElementUtils.hasAnnotation(factoryType, TestConfiguration.class)) {
315+
result.add(factoryType);
316+
}
317+
318+
return result;
319+
}
274320
}
275321
}

spring-modulith-test/src/main/java/org/springframework/modulith/test/ModuleTestAutoConfiguration.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
3030
import org.springframework.core.Ordered;
3131
import org.springframework.core.type.AnnotationMetadata;
32+
import org.springframework.modulith.core.JavaPackage;
3233
import org.springframework.util.StringUtils;
3334

3435
/**
@@ -58,7 +59,7 @@ static class AutoConfigurationAndEntityScanPackageCustomizer implements ImportBe
5859
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
5960

6061
var execution = ((BeanFactory) registry).getBean(ModuleTestExecution.class);
61-
var basePackages = execution.getBasePackages().toList();
62+
var basePackages = execution.getBasePackages().stream().map(JavaPackage::getName).toList();
6263

6364
LOGGER.info("Re-configuring auto-configuration and entity scan packages to: {}.",
6465
StringUtils.collectionToDelimitedString(basePackages, ", "));

spring-modulith-test/src/main/java/org/springframework/modulith/test/ModuleTestExecution.java

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,7 @@
3535
import org.springframework.modulith.core.ApplicationModule;
3636
import org.springframework.modulith.core.ApplicationModules;
3737
import org.springframework.modulith.core.ApplicationModulesFactory;
38-
import org.springframework.modulith.core.JavaPackage;
39-
import org.springframework.modulith.core.PackageName;
38+
import org.springframework.modulith.core.JavaPackages;
4039
import org.springframework.modulith.test.ApplicationModuleTest.BootstrapMode;
4140
import org.springframework.util.ObjectUtils;
4241
import org.springframework.util.StringUtils;
@@ -69,7 +68,7 @@ public class ModuleTestExecution implements Iterable<ApplicationModule> {
6968
private final ApplicationModules modules;
7069
private final List<ApplicationModule> extraIncludes;
7170

72-
private final Supplier<List<JavaPackage>> basePackages;
71+
private final Supplier<JavaPackages> basePackages;
7372
private final Supplier<List<ApplicationModule>> dependencies;
7473

7574
private ModuleTestExecution(ApplicationModuleTest annotation, ApplicationModules modules, ApplicationModule module) {
@@ -89,7 +88,8 @@ private ModuleTestExecution(ApplicationModuleTest annotation, ApplicationModules
8988

9089
var intermediate = Stream.concat(moduleBasePackages, extraPackages);
9190

92-
return Stream.concat(intermediate, sharedBasePackages).distinct().toList();
91+
return Stream.concat(intermediate, sharedBasePackages).distinct()
92+
.collect(Collectors.collectingAndThen(Collectors.toList(), JavaPackages::new));
9393
});
9494

9595
this.dependencies = SingletonSupplier.of(() -> {
@@ -129,8 +129,8 @@ public static Supplier<ModuleTestExecution> of(Class<?> type) {
129129
*
130130
* @return
131131
*/
132-
public Stream<String> getBasePackages() {
133-
return basePackages.get().stream().map(JavaPackage::getName);
132+
public JavaPackages getBasePackages() {
133+
return basePackages.get();
134134
}
135135

136136
public boolean includes(String className) {
@@ -240,14 +240,7 @@ public int hashCode() {
240240
}
241241

242242
private boolean isLocatedInRootPackageOrContainedInBasePackages(String className) {
243-
244-
if (modules.withinRootPackages(className)) {
245-
return true;
246-
}
247-
248-
var candidate = PackageName.ofType(className);
249-
250-
return basePackages.get().stream().map(JavaPackage::getPackageName).anyMatch(it -> it.contains(candidate));
243+
return modules.withinRootPackages(className) || basePackages.get().couldContain(className);
251244
}
252245

253246
private static Stream<ApplicationModule> getExtraModules(ApplicationModuleTest annotation,

0 commit comments

Comments
 (0)