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"/> </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]