Skip to content

Commit cc64d53

Browse files
committed
GH-1495 - Include bean definitions from @TestConfiguration classes in test run.
1 parent 88be84b commit cc64d53

File tree

5 files changed

+115
-31
lines changed

5 files changed

+115
-31
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-1495
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: 60 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;
@@ -30,10 +32,12 @@
3032
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
3133
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
3234
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
35+
import org.springframework.boot.test.context.TestConfiguration;
3336
import org.springframework.context.ConfigurableApplicationContext;
37+
import org.springframework.core.annotation.AnnotatedElementUtils;
3438
import org.springframework.modulith.core.ApplicationModule;
3539
import org.springframework.modulith.core.ApplicationModuleIdentifier;
36-
import org.springframework.modulith.core.JavaPackage;
40+
import org.springframework.modulith.core.ApplicationModules;
3741
import org.springframework.test.context.ContextConfigurationAttributes;
3842
import org.springframework.test.context.ContextCustomizer;
3943
import org.springframework.test.context.ContextCustomizerFactory;
@@ -239,26 +243,13 @@ public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) t
239243

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

242-
var type = factory.getType(name, false);
243-
244-
if (type == null) {
245-
continue;
246-
}
247-
248-
var module = modules.getModuleByType(type)
249-
.filter(Predicate.not(ApplicationModule::isRootModule));
250-
251-
// Not a module type -> pass
252-
if (module.isEmpty()) {
246+
if (include(name, modules, factory)) {
253247
continue;
254248
}
255249

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

258-
// A type of a module bootstrapped -> pass
259-
if (module.map(ApplicationModule::getBasePackage)
260-
.map(JavaPackage::getName)
261-
.filter(packagesIncludedInTestRun::contains).isPresent()) {
252+
if (type == null) {
262253
continue;
263254
}
264255

@@ -271,11 +262,63 @@ public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) t
271262
}
272263
}
273264

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

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 & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
import org.springframework.modulith.core.ApplicationModuleIdentifier;
3838
import org.springframework.modulith.core.ApplicationModules;
3939
import org.springframework.modulith.core.ApplicationModulesFactory;
40-
import org.springframework.modulith.core.JavaPackage;
40+
import org.springframework.modulith.core.JavaPackages;
4141
import org.springframework.modulith.core.PackageName;
4242
import org.springframework.modulith.test.ApplicationModuleTest.BootstrapMode;
4343
import org.springframework.util.Assert;
@@ -72,7 +72,7 @@ public class ModuleTestExecution implements Iterable<ApplicationModule> {
7272
private final ApplicationModules modules;
7373
private final List<ApplicationModule> extraIncludes;
7474

75-
private final Supplier<List<JavaPackage>> basePackages;
75+
private final Supplier<JavaPackages> basePackages;
7676
private final Supplier<List<ApplicationModule>> dependencies;
7777
private final Supplier<List<ApplicationModule>> includedModules;
7878

@@ -93,7 +93,8 @@ private ModuleTestExecution(ApplicationModuleTest annotation, ApplicationModules
9393

9494
var intermediate = Stream.concat(moduleBasePackages, extraPackages);
9595

96-
return Stream.concat(intermediate, sharedBasePackages).distinct().toList();
96+
return Stream.concat(intermediate, sharedBasePackages).distinct()
97+
.collect(Collectors.collectingAndThen(Collectors.toList(), JavaPackages::new));
9798
});
9899

99100
this.dependencies = SingletonSupplier.of(() -> {
@@ -150,8 +151,8 @@ public static Supplier<ModuleTestExecution> of(Class<?> type) {
150151
*
151152
* @return
152153
*/
153-
public Stream<String> getBasePackages() {
154-
return basePackages.get().stream().map(JavaPackage::getName);
154+
public JavaPackages getBasePackages() {
155+
return basePackages.get();
155156
}
156157

157158
public boolean includes(String className) {
@@ -276,14 +277,7 @@ public int hashCode() {
276277
}
277278

278279
private boolean isLocatedInRootPackageOrContainedInBasePackages(String className) {
279-
280-
if (modules.withinRootPackages(className)) {
281-
return true;
282-
}
283-
284-
var candidate = PackageName.ofType(className);
285-
286-
return basePackages.get().stream().map(JavaPackage::getPackageName).anyMatch(it -> it.contains(candidate));
280+
return modules.withinRootPackages(className) || basePackages.get().couldContain(className);
287281
}
288282

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

0 commit comments

Comments
 (0)