PROBLEM
Let’s assume we have a package with the following classes where each class is either annotated with Spring’s @Service, @Component, @Controller or @Repository.
app
├── A.groovy
├── B.groovy
├── C.groovy
├── D.groovy
└── E.groovy
When writing unit test, we want Spring to component scan class A and class B.
SOLUTION
Before we begin, we configure Log4j to log Spring in debug level.
<logger name="org.springframework">
<level value="debug">
</level></logger>
Step 1
If we configure the test class like this…
@ContextConfiguration
class ASpec extends Specification {
@Configuration
@ComponentScan(
basePackageClasses = [A]
)
static class TestConfig {
}
def "..."() {
// ...
}
}
It will scan all Spring components that reside in the same package as class A.
Debugging log:-
[DEBUG] [ClassPathBeanDefinitionScanner] [findCandidateComponents:294] - Identified candidate component class: file [/path/target/classes/app/A.class]
[DEBUG] [ClassPathBeanDefinitionScanner] [findCandidateComponents:294] - Identified candidate component class: file [/path/target/classes/app/B.class]
[DEBUG] [ClassPathBeanDefinitionScanner] [findCandidateComponents:294] - Identified candidate component class: file [/path/target/classes/app/C.class]
[DEBUG] [ClassPathBeanDefinitionScanner] [findCandidateComponents:294] - Identified candidate component class: file [/path/target/classes/app/D.class]
[DEBUG] [ClassPathBeanDefinitionScanner] [findCandidateComponents:294] - Identified candidate component class: file [/path/target/classes/app/E.class]
Step 2
We can set includeFilters to include just class A and class B…
@ContextConfiguration
class ASpec extends Specification {
@Configuration
@ComponentScan(
basePackageClasses = [A],
includeFilters = [@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = [A, B])]
)
static class TestConfig {
}
def "..."() {
// ...
}
}
… but it doesn’t do anything.
Debugging log:-
[DEBUG] [ClassPathBeanDefinitionScanner] [findCandidateComponents:294] - Identified candidate component class: file [/path/target/classes/app/A.class]
[DEBUG] [ClassPathBeanDefinitionScanner] [findCandidateComponents:294] - Identified candidate component class: file [/path/target/classes/app/B.class]
[DEBUG] [ClassPathBeanDefinitionScanner] [findCandidateComponents:294] - Identified candidate component class: file [/path/target/classes/app/C.class]
[DEBUG] [ClassPathBeanDefinitionScanner] [findCandidateComponents:294] - Identified candidate component class: file [/path/target/classes/app/D.class]
[DEBUG] [ClassPathBeanDefinitionScanner] [findCandidateComponents:294] - Identified candidate component class: file [/path/target/classes/app/E.class]
Step 3
To fix this, we set useDefaultFilters to false to disable any automatic detection of classes annotated with Spring’s @Service, @Component, @Controller or @Repository.
@ContextConfiguration
class ASpec extends Specification {
@Configuration
@ComponentScan(
basePackageClasses = [A],
useDefaultFilters = false,
includeFilters = [@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = [A, B])]
)
static class TestConfig {
}
def "..."() {
// ...
}
}
Now, we get the intended behavior.
Debugging log:-
[DEBUG] [ClassPathBeanDefinitionScanner] [findCandidateComponents:294] - Identified candidate component class: file [/path/target/classes/app/A.class]
[DEBUG] [ClassPathBeanDefinitionScanner] [findCandidateComponents:294] - Identified candidate component class: file [/path/target/classes/app/B.class]
Leave a Reply