diff --git a/spring-boot-project/spring-boot-test/build.gradle b/spring-boot-project/spring-boot-test/build.gradle index 2f81d64d4ee5..9c52999be1ba 100644 --- a/spring-boot-project/spring-boot-test/build.gradle +++ b/spring-boot-project/spring-boot-test/build.gradle @@ -59,5 +59,5 @@ dependencies { testImplementation("org.testng:testng") testRuntimeOnly("org.junit.vintage:junit-vintage-engine") + testRuntimeOnly("org.yaml:snakeyaml") } - diff --git a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/YamlPropertySourceFactory.java b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/YamlPropertySourceFactory.java new file mode 100644 index 000000000000..b8e638d6c210 --- /dev/null +++ b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/YamlPropertySourceFactory.java @@ -0,0 +1,72 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.test.context; + +import java.io.IOException; +import java.util.List; + +import org.springframework.boot.env.YamlPropertySourceLoader; +import org.springframework.core.env.CompositePropertySource; +import org.springframework.core.env.PropertySource; +import org.springframework.core.io.Resource; +import org.springframework.core.io.support.EncodedResource; +import org.springframework.core.io.support.PropertySourceFactory; +import org.springframework.util.StringUtils; + +/** + * An implementation of {@link PropertySourceFactory} that delegates the loading of + * {@code PropertySource} to {@link YamlPropertySourceLoader}. If the provided YAML file + * contains multiple documents (separated by {@code ---}), the property sources from later + * documents will take precedence, overriding any conflicting values defined in earlier + * documents. + * + * @author Dmytro Nosan + * @since 3.4.0 + */ +public class YamlPropertySourceFactory implements PropertySourceFactory { + + private final YamlPropertySourceLoader loader = new YamlPropertySourceLoader(); + + @Override + public PropertySource createPropertySource(String name, EncodedResource encodedResource) throws IOException { + Resource resource = encodedResource.getResource(); + String propertySourceName = getPropertySourceName(name, resource); + List> propertySources = this.loader.load(propertySourceName, resource); + return new YamlCompositePropertySource(propertySourceName, propertySources); + } + + private static String getPropertySourceName(String name, Resource resource) { + if (StringUtils.hasText(name)) { + return name; + } + String description = resource.getDescription(); + if (StringUtils.hasText(description)) { + return description; + } + return resource.getClass().getSimpleName() + "@" + System.identityHashCode(resource); + } + + private static final class YamlCompositePropertySource extends CompositePropertySource { + + YamlCompositePropertySource(String name, List> propertySources) { + super(name); + propertySources.forEach(this::addFirstPropertySource); + } + + } + +} diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/YamlPropertySourceFactoryIntegrationTests.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/YamlPropertySourceFactoryIntegrationTests.java new file mode 100644 index 000000000000..91be38b2eece --- /dev/null +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/YamlPropertySourceFactoryIntegrationTests.java @@ -0,0 +1,55 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.test.context; + +import org.junit.jupiter.api.Test; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.Environment; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Integration tests for {@link YamlPropertySourceFactory} with + * {@link TestPropertySource}. + * + * @author Dmytro Nosan + */ +@SpringJUnitConfig +@TestPropertySource(locations = { "classpath:test.yaml", "classpath:test1.yaml" }, + factory = YamlPropertySourceFactory.class) +class YamlPropertySourceFactoryIntegrationTests { + + @Autowired + private Environment environment; + + @Test + void loadProperties() { + assertThat(this.environment.getProperty("spring.bar")).isEqualTo("bar"); + assertThat(this.environment.getProperty("spring.foo")).isEqualTo("baz"); + assertThat(this.environment.getProperty("spring.buzz")).isEqualTo("fazz"); + } + + @Configuration(proxyBeanMethods = false) + static class Config { + + } + +} diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/YamlPropertySourceFactoryTests.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/YamlPropertySourceFactoryTests.java new file mode 100644 index 000000000000..e685b61966b0 --- /dev/null +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/YamlPropertySourceFactoryTests.java @@ -0,0 +1,70 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.test.context; + +import java.io.IOException; + +import org.junit.jupiter.api.Test; + +import org.springframework.core.env.PropertySource; +import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.support.EncodedResource; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.BDDMockito.willReturn; +import static org.mockito.Mockito.spy; + +/** + * Tests for {@link YamlPropertySourceFactory}. + * + * @author Dmytro Nosan + */ +class YamlPropertySourceFactoryTests { + + private final YamlPropertySourceFactory factory = new YamlPropertySourceFactory(); + + @Test + void shouldCreatePropertySourceWithYamlPropertySourceLoaderWithGivenName() throws IOException { + EncodedResource resource = new EncodedResource(new ClassPathResource("test.yaml")); + PropertySource propertySource = this.factory.createPropertySource("test", resource); + assertThat(propertySource.getName()).isEqualTo("test"); + assertProperties(propertySource); + } + + @Test + void shouldCreatePropertySourceWithYamlPropertySourceLoaderWithResourceDescriptionName() throws IOException { + EncodedResource resource = new EncodedResource(new ClassPathResource("test.yaml")); + PropertySource propertySource = this.factory.createPropertySource(null, resource); + assertThat(propertySource.getName()).isEqualTo("class path resource [test.yaml]"); + assertProperties(propertySource); + } + + @Test + void shouldCreatePropertySourceWithYamlPropertySourceLoaderWithGeneratedName() throws IOException { + ClassPathResource resource = spy(new ClassPathResource("test.yaml")); + willReturn(null).given(resource).getDescription(); + PropertySource propertySource = this.factory.createPropertySource(null, new EncodedResource(resource)); + assertThat(propertySource.getName()).startsWith("ClassPathResource@"); + assertProperties(propertySource); + } + + private static void assertProperties(PropertySource propertySource) { + assertThat(propertySource.getProperty("spring.bar")).isEqualTo("bar"); + assertThat(propertySource.getProperty("spring.foo")).isEqualTo("baz"); + } + +} diff --git a/spring-boot-project/spring-boot-test/src/test/resources/test.yaml b/spring-boot-project/spring-boot-test/src/test/resources/test.yaml new file mode 100644 index 000000000000..1ef6bae1dd6d --- /dev/null +++ b/spring-boot-project/spring-boot-test/src/test/resources/test.yaml @@ -0,0 +1,6 @@ +spring: + bar: foo +--- +spring: + bar: bar + foo: baz diff --git a/spring-boot-project/spring-boot-test/src/test/resources/test1.yaml b/spring-boot-project/spring-boot-test/src/test/resources/test1.yaml new file mode 100644 index 000000000000..4b1223247cf5 --- /dev/null +++ b/spring-boot-project/spring-boot-test/src/test/resources/test1.yaml @@ -0,0 +1,2 @@ +spring: + buzz: fazz