Provide a more concise way to customize the system environment when using ApplicationContextRunner
For some specific cases, it is necessary to set systemEnvironment which is different from systemProperties. For example, mocking out a Kubernetes environment. Adding a test for Kubernetes in ConditionalOnCloudPlatformTests is currently not possible.
Thanks for the suggestion.
Adding a test for Kubernetes in ConditionalOnCloudPlatformTests is currently not possible
It is possible, although it's quite cumbersome. It can be achieved by using a custom supplier for the runner's context and replacing the system environment property source:
@Test
void outcomeWhenKubernetesPlatformPresentShouldMatch() {
new ApplicationContextRunner(() -> {
ConfigurableApplicationContext context = new AnnotationConfigApplicationContext();
Map<String, Object> systemEnvironment = new HashMap<>();
systemEnvironment.put("KUBERNETES_SERVICE_HOST", "k8s.example.com");
systemEnvironment.put("KUBERNETES_SERVICE_PORT", "4567");
context.getEnvironment().getPropertySources()
.replace(StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, new MapPropertySource(
StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, systemEnvironment));
return context;
}).withUserConfiguration(KubernetesPlatformConfig.class).run((context) -> assertThat(context).hasBean("foo"));
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnCloudPlatform(CloudPlatform.KUBERNETES)
static class KubernetesPlatformConfig {
@Bean
String foo() {
return "foo";
}
}
We can consider removing some of the boilerplate here by providing methods on the runner specifically for manipulating the system environment. I'm not sure how common this use-case is, so let's see if the rest of the team thinks making it easier is worthwhile.
The use case does not seem very common so I wonder if it warrants a dedicated method on the runner. I am also sightly worried that systemEnvironment and systemProperties can confuse users. The runner has higher-level way to customize things before the context run (Function). Perhaps we could offer an implementation on the side that users can apply for this use case?
Thanks, @snicoll. I'm not sure that the Function would help here as there's no good way to plug this into an existing ApplicationContextRunner. withInitializer does not work for the example above as @ConditionalOnCloudPlatform(CloudPlatform.KUBERNETES) is evaluated before the initializer is called. Confusingly, this does work:
@Test
void outcomeWhenKubernetesPlatformPresentShouldMatch() {
new ApplicationContextRunner().withInitializer((context) -> {
Map<String, Object> systemEnvironment = new HashMap<>();
systemEnvironment.put("KUBERNETES_SERVICE_HOST", "k8s.example.com");
systemEnvironment.put("KUBERNETES_SERVICE_PORT", "4567");
context.getEnvironment().getPropertySources()
.replace(StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, new MapPropertySource(
StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, systemEnvironment));
}).withUserConfiguration(KubernetesPlatformConfig.class).run((context) -> assertThat(context).hasBean("foo"));
}
@Configuration(proxyBeanMethods = false)
static class KubernetesPlatformConfig {
@Configuration(proxyBeanMethods = false)
@ConditionalOnCloudPlatform(CloudPlatform.KUBERNETES)
static class SomeInnerConfiguration {
@Bean
String foo() {
return "foo";
}
}
}
Nesting the use of @ConditionalOnCloudPlatform(CloudPlatform.KUBERNETES) defers its evaluation until after the initializer has been called.
Flagging for a team meeting as I'd like to discuss whether we should revise the ordering in the runner:
https://github.com/spring-projects/spring-boot/blob/c996e4335af807818205e9b3b3300598daf038d2/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/runner/AbstractApplicationContextRunner.java#L405-L410
The current arrangement means that conditions on the registered classes themselves are evaluated before the initializers and bean registrations are applied.
I've opened https://github.com/spring-projects/spring-boot/issues/31280 to re-order things in ApplicationContextRunner. We'll leave this issue open for now while we decide if we want to do anything to ease configuring the system environment.