Skip to content

ApplicationContextRunner has inconsistent behaviour with duplicate auto-configuration class names #17963

Closed
@michael-simons

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 ApplicationContextRunnerdoes 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.

autoconfigissue.zip

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions