diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/features/external-config.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/features/external-config.adoc index 00c96ea941c1..14aa8cd9aba3 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/features/external-config.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/features/external-config.adoc @@ -525,7 +525,7 @@ The lines immediately before and after the separator must not be same comment pr TIP: Multi-document property files are often used in conjunction with activation properties such as `spring.config.activate.on-profile`. See the xref:features/external-config.adoc#features.external-config.files.activation-properties[next section] for details. -WARNING: Multi-document property files cannot be loaded by using the javadoc:org.springframework.context.annotation.PropertySource[format=annotation] or javadoc:org.springframework.test.context.TestPropertySource[format=annotation] annotations. +WARNING: Multi-document property files cannot be loaded by using the javadoc:org.springframework.context.annotation.PropertySource[format=annotation], javadoc:org.springframework.test.context.TestPropertySource[format=annotation] or javadoc:org.springframework.boot.test.context.TestYamlPropertySource[format=annotation] annotations. diff --git a/spring-boot-project/spring-boot-test/build.gradle b/spring-boot-project/spring-boot-test/build.gradle index dd73e9729a06..33b3697cab2d 100644 --- a/spring-boot-project/spring-boot-test/build.gradle +++ b/spring-boot-project/spring-boot-test/build.gradle @@ -58,5 +58,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/TestYamlPropertySource.java b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/TestYamlPropertySource.java new file mode 100644 index 000000000000..040f863eec12 --- /dev/null +++ b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/TestYamlPropertySource.java @@ -0,0 +1,98 @@ +/* + * Copyright 2012-2025 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.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Repeatable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.core.annotation.AliasFor; +import org.springframework.test.context.TestPropertySource; + +/** + * {@code @TestYamlPropertySource} is an annotation that can be applied to a test class to + * configure the locations of YAML files and inlined properties to be added to the + * Environment's set of PropertySources for an ApplicationContext for integration tests. + *
+ * Provides a convenient alternative for + * {@code @TestPropertySource(locations = "...", factory = YamlPropertySourceFactory.class)}. + *
+ * {@code @TestYamlPropertySource} should be considered as {@code @TestPropertySource} but + * for YAML files. It intentionally does not support multi-document YAML files to maintain + * consistency with the behavior of {@code @TestPropertySource}. + * + * @author Dmytro Nosan + * @since 3.5.0 + * @see YamlPropertySourceFactory + * @see TestPropertySource + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Inherited +@TestPropertySource(factory = YamlPropertySourceFactory.class) +@Repeatable(TestYamlPropertySources.class) +public @interface TestYamlPropertySource { + + /** + * Alias for {@link TestPropertySource#value()}. + * @return The resource locations of YAML files. + * @see TestPropertySource#value() for more details. + */ + @AliasFor(attribute = "value", annotation = TestPropertySource.class) + String[] value() default {}; + + /** + * Alias for {@link TestPropertySource#locations()}. + * @return The resource locations of YAML files. + * @see TestPropertySource#locations() for more details. + */ + @AliasFor(attribute = "locations", annotation = TestPropertySource.class) + String[] locations() default {}; + + /** + * Alias for {@link TestPropertySource#inheritLocations()}. + * @return Whether test property source {@link #locations} from superclasses and + * enclosing classes should be inherited. + * @see TestPropertySource#inheritLocations() for more details. + */ + @AliasFor(attribute = "inheritLocations", annotation = TestPropertySource.class) + boolean inheritLocations() default true; + + /** + * Alias for {@link TestPropertySource#properties()}. + * @return Inlined properties in the form of key-value pairs that + * should be added to the Environment + * @see TestPropertySource#properties() for more details. + */ + @AliasFor(attribute = "properties", annotation = TestPropertySource.class) + String[] properties() default {}; + + /** + * Alias for {@link TestPropertySource#inheritProperties()}. + * @return Whether inlined test {@link #properties} from superclasses and enclosing + * classes should be inherited. + * @see TestPropertySource#inheritProperties() for more details. + */ + @AliasFor(attribute = "inheritProperties", annotation = TestPropertySource.class) + boolean inheritProperties() default true; + +} diff --git a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/TestYamlPropertySources.java b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/TestYamlPropertySources.java new file mode 100644 index 000000000000..6ef20035fb91 --- /dev/null +++ b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/TestYamlPropertySources.java @@ -0,0 +1,46 @@ +/* + * Copyright 2012-2025 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.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * {@code @TestYamlPropertySources} is a container for one or more + * {@link TestYamlPropertySource @TestYamlPropertySource} declarations. + * + * @author Dmytro Nosan + * @since 3.5.0 + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Inherited +public @interface TestYamlPropertySources { + + /** + * An array of one or more {@link TestYamlPropertySource @TestYamlPropertySource} + * declarations. + * @return {@link TestYamlPropertySource @TestYamlPropertySource} annotations. + */ + TestYamlPropertySource[] value(); + +} 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..0579e5bf4ffa --- /dev/null +++ b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/YamlPropertySourceFactory.java @@ -0,0 +1,70 @@ +/* + * Copyright 2012-2025 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.Collections; +import java.util.List; + +import org.springframework.boot.env.YamlPropertySourceLoader; +import org.springframework.core.env.MapPropertySource; +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.Assert; +import org.springframework.util.StringUtils; + +/** + * An implementation of {@link PropertySourceFactory} that delegates the loading of + * {@code PropertySource} to {@link YamlPropertySourceLoader}. + *
+ * Even though {@link YamlPropertySourceLoader} supports multi-document YAML files, the
+ * {@code YamlPropertySourceFactory} intentionally does not allow this.
+ *
+ * @author Dmytro Nosan
+ * @since 3.5.0
+ * @see TestYamlPropertySource
+ */
+public class YamlPropertySourceFactory implements PropertySourceFactory {
+
+ private static 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