Spring Boot’s externalized configuration is a powerful feature. But with great power comes, ahem, at least sometimes, a bunch of unused properties polluting your environment. That what=where-does-this-get-used
in application-doineedthem.properties
? You might be able to remove it.
I created a solution that you can incorporate into your test setup, so
First, create an EnvironmentPostProcessor
, which “[a]llows for customization of the application’s Environment prior to the application context being refreshed.” This should be the correct time to snapshot your properties and inject property-usage tracking capabilities.
public class UnusedPropertiesEnvironmentPostProcessor implements EnvironmentPostProcessor {
private static Set<String> getAllPropertyNames(ConfigurableEnvironment environment) {
var result = new HashSet<String>();
for (var ps : environment.getPropertySources()) {
// One can pick one's own heuristic on what to track.
// `instance of OriginTrackedMapPropertySource` should cover externalized config files
// such as application.properties.
if (ps instanceof OriginTrackedMapPropertySource otps) {
result.addAll(Arrays.asList(otps.getPropertyNames()));
}
}
return result;
}
@Override
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
var propertyNames = getAllPropertyNames(environment);
environment.getPropertySources().addFirst(new PropertySource<>("Usage tracking property source adapter") {
@Override
public Object getProperty(String name) {
if ("unused.properties".equals(name)) {
return propertyNames;
}
propertyNames.remove(name);
return null;
}
});
}
}
Essentially, we’re adding a dummy PropertySource
instance that gets consulted before all the others. For every getProperty(String name)
invocation, it will simply mark that property as used, i.e. remove it from the set of all previously detected properties, and, by returning null
, will let the next PropertySource
instance in line handle the call.
Whenever one retrieves the value of the unused.properties
property, one gets the set of the properties that haven’t been used anywhere until this point in time.
Next, create a spring.factories
file under src/test/resources/META-INF
with the following content to bootstrap the above EnvironmentPostProcessor
instance:
org.springframework.boot.env.EnvironmentPostProcessor=\
replace.with.your.package.UnusedPropertiesEnvironmentPostProcessor
Finally, we can use our detection mechanism in a @SpringBootTest
like so:
@SpringBootTest
class UnusedPropertiesTest {
@Value("${unused.properties}")
Set<String> unusedProperties;
@Test
void containsNoUnusedProperties() {
assertThat(unusedProperties).isEmpty();
}
}
If you have e.g. a property foo=bar
in your application.properties
that you do not @Value("${foo}")
anywhere, this test is going to fail.