Considering following integration test annotations:
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE,
properties = "spring.main.allow-bean-definition-overriding=true")
@ContextConfiguration(classes = {WorkerTestConfig.class})
//@Import(value = {WorkerTestConfig.class})
@ActiveProfiles({"dev","test"})
public class NumberServiceITest {
The role of WorkestTestConfig is to override real bean/set of beans during integration startup, whenever I use @ContextConfiguration
the real bean is backing off and the one from the WorkerTestConfig is used, whenever I use @Import
the real bean is still created and fails the test.
The WorkerTestConfig
itself is as trivial as possible:
@TestConfiguration
public class WorkerTestConfig {
@Primary
@Bean
public ScheduledExecutorService taskExecutor() {
return DirectExecutorFactory.createSameThreadExecutor();
}
}
can anyone explain please yet another magical behavior of @SpringBootTest annotation? If you reproduce the same behaviour please confirm so I can go to issue tracker, as I've seen people using @Import
with @SpringBootTest
here on SO and nothing prohibits it in spring boot docs:
https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-testing.html#boot-features-testing-spring-boot-applications-excluding-config
Totally puzzled on what's going on.
Version: 2.1.2.RELEASE
Update:
Also tried to remove the real bean to see if the issue is with just overriding, but @Import
annotation is just dead in the water, does not work -> unable even to create a bean, @ContextConfiguration has additive/overriding behavior, import does nothing at all.
Fully qualified import for the annotation is:
import org.springframework.context.annotation.Import;
Also tried to change from @TestConfiguration
to @Configuration
just for the sake of it, nothing at all. DEAD.
Update 2:
The @Import
works with standard spring test though :
@RunWith(SpringRunner.class)
@ContextConfiguration(classes = {Some.class,
Some2WhichDependsOnWorkerTestConfig.class})
@Import(WorkerTestConfig.class)
@ActiveProfiles("test")
public class SomeOtherTest {
The order that @Import
classes are processed when they are used on tests isn't defined. The @Import
feature for tests was primarily added to allow additional beans to be registered easily, there was no intention of it being used to replace bean definitions.
If you want to dig into the weeds and see exactly what's going on you can open ConfigurationClassParser
and add a conditional breakpoint in doProcessConfigurationClass
. Add the following condition code:
System.err.println(configClass);
return false;
Now if you debug the application you'll get addition output as the configuration classes are processed.
When you use classes
annotation attribute without the @Import
you'll see:
ConfigurationClass: beanName 'demoImportBugApplication', com.example.demoimportbug.DemoImportBugApplication
ConfigurationClass: beanName 'original', class path resource [com/example/demoimportbug/first/Original.class]
ConfigurationClass: beanName 'workerConfig', class path resource [com/example/demoimportbug/first/WorkerConfig.class]
ConfigurationClass: beanName 'null', class path resource [org/springframework/scheduling/annotation/ProxyAsyncConfiguration.class]
ConfigurationClass: beanName 'null', class path resource [org/springframework/scheduling/annotation/ProxyAsyncConfiguration.class]
ConfigurationClass: beanName 'someTestSecondConfiguration', com.example.demoimportbug.second.SomeTestSecondConfiguration
ConfigurationClass: beanName 'null', class path resource [org/springframework/boot/autoconfigure/context/PropertyPlaceholderAutoConfiguration.class]
ConfigurationClass: beanName 'null', class path resource [org/springframework/boot/autoconfigure/task/TaskExecutionAutoConfiguration.class]
ConfigurationClass: beanName 'null', class path resource [org/springframework/boot/autoconfigure/cache/CacheAutoConfiguration.class]
ConfigurationClass: beanName 'null', class path resource [org/springframework/boot/autoconfigure/cache/GenericCacheConfiguration.class]
ConfigurationClass: beanName 'null', class path resource [org/springframework/boot/autoconfigure/cache/SimpleCacheConfiguration.class]
ConfigurationClass: beanName 'null', class path resource [org/springframework/boot/autoconfigure/cache/NoOpCacheConfiguration.class]
ConfigurationClass: beanName 'null', class path resource [org/springframework/boot/autoconfigure/context/ConfigurationPropertiesAutoConfiguration.class]
ConfigurationClass: beanName 'null', class path resource [org/springframework/boot/autoconfigure/info/ProjectInfoAutoConfiguration.class]
ConfigurationClass: beanName 'null', class path resource [org/springframework/boot/autoconfigure/task/TaskSchedulingAutoConfiguration.class]
When you use the @Import
without the classes
attribute you'll get:
ConfigurationClass: beanName 'org.springframework.boot.test.context.ImportsContextCustomizer$ImportsConfiguration', org.springframework.boot.test.context.ImportsContextCustomizer$ImportsConfiguration
ConfigurationClass: beanName 'null', class path resource [com/example/demoimportbug/first/SomeFirstUsingSecondConfiguration.class]
ConfigurationClass: beanName 'null', class path resource [com/example/demoimportbug/second/SomeTestSecondConfiguration.class]
ConfigurationClass: beanName 'demoImportBugApplication', com.example.demoimportbug.DemoImportBugApplication
ConfigurationClass: beanName 'original', class path resource [com/example/demoimportbug/first/Original.class]
ConfigurationClass: beanName 'workerConfig', class path resource [com/example/demoimportbug/first/WorkerConfig.class]
ConfigurationClass: beanName 'null', class path resource [org/springframework/scheduling/annotation/ProxyAsyncConfiguration.class]
ConfigurationClass: beanName 'null', class path resource [org/springframework/scheduling/annotation/ProxyAsyncConfiguration.class]
ConfigurationClass: beanName 'null', class path resource [org/springframework/boot/autoconfigure/context/PropertyPlaceholderAutoConfiguration.class]
ConfigurationClass: beanName 'null', class path resource [org/springframework/boot/autoconfigure/task/TaskExecutionAutoConfiguration.class]
ConfigurationClass: beanName 'null', class path resource [org/springframework/boot/autoconfigure/cache/CacheAutoConfiguration.class]
ConfigurationClass: beanName 'null', class path resource [org/springframework/boot/autoconfigure/cache/GenericCacheConfiguration.class]
ConfigurationClass: beanName 'null', class path resource [org/springframework/boot/autoconfigure/cache/SimpleCacheConfiguration.class]
ConfigurationClass: beanName 'null', class path resource [org/springframework/boot/autoconfigure/cache/NoOpCacheConfiguration.class]
ConfigurationClass: beanName 'null', class path resource [org/springframework/boot/autoconfigure/context/ConfigurationPropertiesAutoConfiguration.class]
ConfigurationClass: beanName 'null', class path resource [org/springframework/boot/autoconfigure/info/ProjectInfoAutoConfiguration.class]
ConfigurationClass: beanName 'null', class path resource [org/springframework/boot/autoconfigure/task/TaskSchedulingAutoConfiguration.class]
The first version loads WorkerConfig
before SomeTestSecondConfiguration
whereas the second version loads SomeTestSecondConfiguration
before WorkerConfig
.
You'll also notice that the second version has a ImportsContextCustomizer$ImportsConfiguration
class which is the thing that triggers the additional imports.
If you look at SpringBootTestContextBootstrapper
you can see in the getOrFindConfigurationClasses
method that ordering is defined and your additional test classes will always be listed after the primary configuration.
tl;dr If you need defined ordering use the classes
attribute. If you want to add additional beans and you aren't trying to override anything use @Import
.
You might also want to look at @MockBean
which provides a more robust way to replace a bean with a mock.