Skip to content

Allow @PropertySource to be specified on a test class [SPR-10232] #14865

Closed
@spring-projects-issues

Description

@spring-projects-issues

Paul Tomlin opened SPR-10232 and commented

Overview

When using the TestContext framework it would be nice to easily be able to specify some PropertySources to be added to the Environment, in much the same way as @ActiveProfiles allows specifying the active profiles.

Ideally, something like below would result in a ResourcePropertySource being registered with the environment of the test's ApplicationContext prior to refresh:

@ContextConfiguration
@PropertySource("classpath:foo.properties")
public class MyTest {
   // ...
}

Work-around

In Spring Framework 3.1, there is a workaround, but it's not nearly as tidy as the above proposal.

@ContextConfiguration(
  locations = { ... },
  loader = MyTest.CustomeContextLoader.class
)
public class MyTest {
  public static class CustomContextLoader extends GenericXmlContextLoader {
    @Override
    protected void customizeContext(GenericApplicationContext context) {
      // exception handling elided
      context.getEnvironment()
             .getPropertySources()
             .addFirst(new ResourcePropertySource("classpath:foo.properties"));
    }
  }
}

There may be something in 3.2.x, probably as a result of #13650 and ApplicationContextInitializer, but ContextLoaderUtils doesn't seem to suggest so, and I'm not yet familiar enough to know.


Analysis

  • As with @ActiveProfiles, @PropertySource declarations on test classes should be inherited by default but overridable.
  • @PropertySource is not an @Inherited annotation, but AnnotationUtils.findAnnotationDeclaringClass() should take care of this.
  • Inheritance and overriding behavior of @PropertySource in integration tests must be consistent with the existing behavior in @Configuration classes.
    • See code snippets from ConfigurationClassPostProcessor and ConfigurationClassParser below.
  • As far as possible, the existing business logic in ConfigurationClassParser.processPropertySource() should be reused and not duplicated in the testing framework; in other words, consider extracting the existing logic into a static utility method or similar.
  • The context cache key (i.e., MergedContextConfiguration) must take test property sources into account.
Relevant code from ConfigurationClassPostProcessor
// ...
// Handle any @PropertySource annotations
Stack<PropertySource<?>> parsedPropertySources = parser.getPropertySources();
if (!parsedPropertySources.isEmpty()) {
	if (!(this.environment instanceof ConfigurableEnvironment)) {
		logger.warn("Ignoring @PropertySource annotations. " +
				"Reason: Environment must implement ConfigurableEnvironment");
	}
	else {
		MutablePropertySources envPropertySources = ((ConfigurableEnvironment)this.environment).getPropertySources();
		while (!parsedPropertySources.isEmpty()) {
			envPropertySources.addLast(parsedPropertySources.pop());
		}
	}
}
// ...
Relevant code from ConfigurationClassParser
private void processPropertySource(AnnotationAttributes propertySource) throws IOException {
	String name = propertySource.getString("name");
	String[] locations = propertySource.getStringArray("value");
	int nLocations = locations.length;
	if (nLocations == 0) {
		throw new IllegalArgumentException("At least one @PropertySource(value) location is required");
	}
	for (int i = 0; i < nLocations; i++) {
		locations[i] = this.environment.resolveRequiredPlaceholders(locations[i]);
	}
	ClassLoader classLoader = this.resourceLoader.getClassLoader();
	if (!StringUtils.hasText(name)) {
		for (String location : locations) {
			this.propertySources.push(new ResourcePropertySource(location, classLoader));
		}
	}
	else {
		if (nLocations == 1) {
			this.propertySources.push(new ResourcePropertySource(name, locations[0], classLoader));
		}
		else {
			CompositePropertySource ps = new CompositePropertySource(name);
			for (String location : locations) {
				ps.addPropertySource(new ResourcePropertySource(location, classLoader));
			}
			this.propertySources.push(ps);
		}
	}
}

Affects: 3.1 GA

Issue Links:

Referenced from: commits 3210041

4 votes, 8 watchers

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions