ApplicationContextRunner has inconsistent behaviour with duplicate auto-configuration class names #17963
Description
Given the following configurations:
package com.example.demo.a;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.example.outside_app.SomeService;
@Configuration
public class SomeConfig {
@Bean
public SomeService someServiceFromA() {
return new SomeService();
}
}
and in another package, under the same, simple name:
package com.example.demo.b;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.example.outside_app.SomeService;
@Configuration
public class SomeConfig {
@Bean
public SomeService someServiceFromB() {
return new SomeService();
}
}
Spring Framework refuses to work with two configuration classes having the same bean name (someConfig
). This is fine, the error is usable.
However, the ApplicationContextRunner
does not do this and takes in the following configuration
@Test
public void f2() {
new ApplicationContextRunner()
.withUserConfiguration(com.example.demo.a.SomeConfig.class, com.example.demo.b.SomeConfig.class)
.run(ctx -> {
assertThat(ctx).hasBean("someServiceFromB");
assertThat(ctx).hasBean("someServiceFromA");
});
}
And fails the test by silently ignoring or overwriting the first configuration given.
While it would have been noticed in this case, you don't notice it when you test for the absence of one bean and the presence of another. As one config has maybe silently dropped, conditions are not tested.
This happens also with overlapping autoconfigurations having the same class name and this is where I actually found the issue:
Spring Boot and or Spring Framework do apparently support autoconfigurations with the same simple class name.
Given this bean
package com.example.outside_app;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class JpaRepositoriesAutoConfiguration {
@Bean
public SomeService someServiceFromAutoConfig() {
return new SomeService();
}
}
and Spring Factories containing
org.springframework.boot.autoconfigure.EnableAutoConfiguration = com.example.outside_app.JpaRepositoriesAutoConfiguration
I can test my application:
@RunWith(SpringRunner.class)
@SpringBootTest
public class DemoApplicationTests {
@Autowired
private Map<String, SomeService> someServices;
@Test
public void contextLoads() {
assertThat(someServices).containsKeys("someServiceFromAutoConfig");
}
}
However, the ApplicationContextRunner
disagrees and
@Test
public void onlyAutoConfig() {
new ApplicationContextRunner()
.withConfiguration(
AutoConfigurations.of(
org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration.class,
com.example.outside_app.JpaRepositoriesAutoConfiguration.class
))
.run(ctx -> assertThat(ctx).hasBean("someServiceFromAutoConfig"));
}
fails.
While the duplicate names need good reasons and are of course a thing to debate, the application context runner should agree during tests with the framework to prevent errors in custom starters or applications, depending on what is actually tested.
I have attached the demo project.