Skip to content

Commit

Permalink
Add YamlPropertySourceFactory and @TestYamlPropertySource
Browse files Browse the repository at this point in the history
that can be applied to a test class to add PropertySource
loaded from YAML files to the Environment

This commit introduces new functionality to load properties
from YAML files in tests. The @TestYamlPropertySource annotation
is added to allow test classes to specify YAML files as property sources.

The @TestYamlPropertySource provides a convenient alternative for @TestPropertySource(factory=YamlPropertySourceFactory.class).

The YamlPropertySourceFactory class is also introduced to enable the loading of YAML files into the Environment through @TestPropertySource.
  • Loading branch information
nosan committed Nov 16, 2024
1 parent 68022ef commit 96dd0a8
Show file tree
Hide file tree
Showing 12 changed files with 380 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ Sources are considered in the following order:
. `properties` attribute on your tests.
Available on javadoc:org.springframework.boot.test.context.SpringBootTest[format=annotation] and the xref:testing/spring-boot-applications.adoc#testing.spring-boot-applications.autoconfigured-tests[test annotations for testing a particular slice of your application].
. javadoc:{url-spring-framework-javadoc}/org.springframework.test.context.DynamicPropertySource[format=annotation] annotations in your tests.
. javadoc:{url-spring-framework-javadoc}/org.springframework.test.context.TestPropertySource[format=annotation] annotations on your tests.
. javadoc:{url-spring-framework-javadoc}/org.springframework.test.context.TestPropertySource[format=annotation] or javadoc:org.springframework.boot.test.context.TestYamlPropertySource[format=annotation] annotations on your tests.
. xref:using/devtools.adoc#using.devtools.globalsettings[Devtools global settings properties] in the `$HOME/.config/spring-boot` directory when devtools is active.

Config data files are considered in the following order:
Expand Down Expand Up @@ -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 `@PropertySource` or `@TestPropertySource` annotations.
WARNING: Multi-document property files cannot be loaded by using the `@PropertySource`, `@TestPropertySource` or `@TestYamlPropertySource` annotations.



Expand Down Expand Up @@ -640,7 +640,7 @@ TIP: Properties that use the `[index]` notation can be bound to Java `List` or `
For more details see the xref:features/external-config.adoc#features.external-config.typesafe-configuration-properties[] section below.

WARNING: YAML files cannot be loaded by using the `@PropertySource` or `@TestPropertySource` annotations.
So, in the case that you need to load values that way, you need to use a properties file.
So, in the case that you need to load values that way, you need to use a properties file or use `@TestYamlPropertySource`



Expand Down
2 changes: 1 addition & 1 deletion spring-boot-project/spring-boot-test/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -59,5 +59,5 @@ dependencies {
testImplementation("org.testng:testng")

testRuntimeOnly("org.junit.vintage:junit-vintage-engine")
testRuntimeOnly("org.yaml:snakeyaml")
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/*
* 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.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.
* <p>
* Provides a convenient alternative for
* {@code @TestPropertySource(locations = "...", factory = YamlPropertySourceFactory.class)}.
* <p>
* {@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 <em>inherited</em>.
* @see TestPropertySource#inheritLocations() for more details.
*/
@AliasFor(attribute = "inheritLocations", annotation = TestPropertySource.class)
boolean inheritLocations() default true;

/**
* Alias for {@link TestPropertySource#properties()}.
* @return <em>Inlined properties</em> in the form of <em>key-value</em> 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 <em>inherited</em>.
* @see TestPropertySource#inheritProperties() for more details.
*/
@AliasFor(attribute = "inheritProperties", annotation = TestPropertySource.class)
boolean inheritProperties() default true;

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* 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.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();

}
Original file line number Diff line number Diff line change
@@ -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 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}.
* <p>
* 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<PropertySource<?>> propertySources = loader.load(propertySourceName, resource);
Assert.isTrue(propertySources.size() <= 1, () -> resource + " is a multi-document YAML file");
if (propertySources.isEmpty()) {
return new MapPropertySource(name, Collections.emptyMap());
}
return propertySources.get(0);
}

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);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* 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.junit.jupiter.SpringJUnitConfig;

import static org.assertj.core.api.Assertions.assertThat;

/**
* Integration tests for {@link YamlPropertySourceFactory} with
* {@link TestYamlPropertySource}.
*
* @author Dmytro Nosan
*/
@SpringJUnitConfig
@TestYamlPropertySource({ "test.yaml", "test1.yaml" })
@TestYamlPropertySource(locations = "test2.yaml", properties = "key:value")
class TestYamlPropertySourceIntegrationTests {

@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");
assertThat(this.environment.getProperty("spring.boot")).isEqualTo("boot");
assertThat(this.environment.getProperty("key")).isEqualTo("value");
}

@Configuration(proxyBeanMethods = false)
static class Config {

}

}
Loading

0 comments on commit 96dd0a8

Please sign in to comment.