Support custom properties file formats in @TestPropertySource

Spring Framework 4.3 introduced the `PropertySourceFactory` SPI for use
with `@PropertySource` on `@Configuration` classes; however, prior to
this commit there was no mechanism to support custom properties file
formats in `@TestPropertySource` for integration tests.

This commit introduces support for configuring a custom
`PropertySourceFactory` via a new `factory` attribute in
`@TestPropertySource` in order to support custom file formats such as
JSON, YAML, etc.

For example, if you create a YamlPropertySourceFactory, you can use it
in integration tests as follows.

@SpringJUnitConfig
@TestPropertySource(locations = "/test.yaml", factory = YamlPropertySourceFactory.class)
class MyTestClass { /* ... /* }

If a custom factory is not specified, traditional `*.properties` and
`*.xml` based `java.util.Properties` file formats are supported, which
was the existing behavior.

Closes gh-30981
This commit is contained in:
Sam Brannen 2023-08-04 12:10:07 +03:00
parent b80872b762
commit 04cce0bafd
24 changed files with 622 additions and 130 deletions

View File

@ -16,7 +16,7 @@ SPI, but `@TestPropertySource` is not supported with implementations of the olde
`ContextLoader` SPI. `ContextLoader` SPI.
Implementations of `SmartContextLoader` gain access to merged test property source values Implementations of `SmartContextLoader` gain access to merged test property source values
through the `getPropertySourceLocations()` and `getPropertySourceProperties()` methods in through the `getPropertySourceDescriptors()` and `getPropertySourceProperties()` methods in
`MergedContextConfiguration`. `MergedContextConfiguration`.
==== ====
@ -26,8 +26,11 @@ through the `getPropertySourceLocations()` and `getPropertySourceProperties()` m
You can configure test properties files by using the `locations` or `value` attribute of You can configure test properties files by using the `locations` or `value` attribute of
`@TestPropertySource`. `@TestPropertySource`.
Both traditional and XML-based properties file formats are supported -- for example, By default, both traditional and XML-based `java.util.Properties` file formats are
`"classpath:/com/example/test.properties"` or `"file:///path/to/file.xml"`. supported -- for example, `"classpath:/com/example/test.properties"` or
`"file:///path/to/file.xml"`. As of Spring Framework 6.1, you can configure a custom
`PropertySourceFactory` via the `factory` attribute in `@TestPropertySource` in order to
support a different file format such as JSON, YAML, etc.
Each path is interpreted as a Spring `Resource`. A plain path (for example, Each path is interpreted as a Spring `Resource`. A plain path (for example,
`"test.properties"`) is treated as a classpath resource that is relative to the package `"test.properties"`) is treated as a classpath resource that is relative to the package
@ -35,8 +38,8 @@ in which the test class is defined. A path starting with a slash is treated as a
absolute classpath resource (for example: `"/org/example/test.xml"`). A path that absolute classpath resource (for example: `"/org/example/test.xml"`). A path that
references a URL (for example, a path prefixed with `classpath:`, `file:`, or `http:`) is references a URL (for example, a path prefixed with `classpath:`, `file:`, or `http:`) is
loaded by using the specified resource protocol. Resource location wildcards (such as loaded by using the specified resource protocol. Resource location wildcards (such as
`**/*.properties`) are not permitted: Each location must evaluate to exactly one `{asterisk}{asterisk}/{asterisk}.properties`) are not permitted: Each location must
`.properties` or `.xml` resource. evaluate to exactly one properties resource.
The following example uses a test properties file: The following example uses a test properties file:

View File

@ -85,6 +85,7 @@ dependencies {
testRuntimeOnly("org.junit.vintage:junit-vintage-engine") { testRuntimeOnly("org.junit.vintage:junit-vintage-engine") {
exclude group: "junit", module: "junit" exclude group: "junit", module: "junit"
} }
testRuntimeOnly("org.yaml:snakeyaml")
} }
// Prevent xml-apis from being used so that the corresponding XML APIs from // Prevent xml-apis from being used so that the corresponding XML APIs from

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2022 the original author or authors. * Copyright 2002-2023 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -20,10 +20,12 @@ import java.io.Serializable;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set; import java.util.Set;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextInitializer; import org.springframework.context.ApplicationContextInitializer;
import org.springframework.core.io.support.PropertySourceDescriptor;
import org.springframework.core.style.DefaultToStringStyler; import org.springframework.core.style.DefaultToStringStyler;
import org.springframework.core.style.SimpleValueStyler; import org.springframework.core.style.SimpleValueStyler;
import org.springframework.core.style.ToStringCreator; import org.springframework.core.style.ToStringCreator;
@ -91,6 +93,8 @@ public class MergedContextConfiguration implements Serializable {
private final String[] activeProfiles; private final String[] activeProfiles;
private final List<PropertySourceDescriptor> propertySourceDescriptors;
private final String[] propertySourceLocations; private final String[] propertySourceLocations;
private final String[] propertySourceProperties; private final String[] propertySourceProperties;
@ -151,7 +155,7 @@ public class MergedContextConfiguration implements Serializable {
* @param activeProfiles the merged active bean definition profiles * @param activeProfiles the merged active bean definition profiles
* @param contextLoader the resolved {@code ContextLoader} * @param contextLoader the resolved {@code ContextLoader}
* @param cacheAwareContextLoaderDelegate a cache-aware context loader * @param cacheAwareContextLoaderDelegate a cache-aware context loader
* delegate with which to retrieve the parent context * delegate with which to retrieve the parent {@code ApplicationContext}
* @param parent the parent configuration or {@code null} if there is no parent * @param parent the parent configuration or {@code null} if there is no parent
* @since 3.2.2 * @since 3.2.2
*/ */
@ -172,9 +176,10 @@ public class MergedContextConfiguration implements Serializable {
*/ */
public MergedContextConfiguration(MergedContextConfiguration mergedConfig) { public MergedContextConfiguration(MergedContextConfiguration mergedConfig) {
this(mergedConfig.testClass, mergedConfig.locations, mergedConfig.classes, this(mergedConfig.testClass, mergedConfig.locations, mergedConfig.classes,
mergedConfig.contextInitializerClasses, mergedConfig.activeProfiles, mergedConfig.propertySourceLocations, mergedConfig.contextInitializerClasses, mergedConfig.activeProfiles,
mergedConfig.propertySourceProperties, mergedConfig.contextCustomizers, mergedConfig.propertySourceDescriptors, mergedConfig.propertySourceProperties,
mergedConfig.contextLoader, mergedConfig.cacheAwareContextLoaderDelegate, mergedConfig.parent); mergedConfig.contextCustomizers, mergedConfig.contextLoader,
mergedConfig.cacheAwareContextLoaderDelegate, mergedConfig.parent);
} }
/** /**
@ -196,10 +201,13 @@ public class MergedContextConfiguration implements Serializable {
* @param propertySourceProperties the merged {@code PropertySource} properties * @param propertySourceProperties the merged {@code PropertySource} properties
* @param contextLoader the resolved {@code ContextLoader} * @param contextLoader the resolved {@code ContextLoader}
* @param cacheAwareContextLoaderDelegate a cache-aware context loader * @param cacheAwareContextLoaderDelegate a cache-aware context loader
* delegate with which to retrieve the parent context * delegate with which to retrieve the parent {@code ApplicationContext}
* @param parent the parent configuration or {@code null} if there is no parent * @param parent the parent configuration or {@code null} if there is no parent
* @since 4.1 * @since 4.1
* @deprecated since 6.1 in favor of
* {@link #MergedContextConfiguration(Class, String[], Class[], Set, String[], List, String[], Set, ContextLoader, CacheAwareContextLoaderDelegate, MergedContextConfiguration)}
*/ */
@Deprecated(since = "6.1")
public MergedContextConfiguration(Class<?> testClass, @Nullable String[] locations, @Nullable Class<?>[] classes, public MergedContextConfiguration(Class<?> testClass, @Nullable String[] locations, @Nullable Class<?>[] classes,
@Nullable Set<Class<? extends ApplicationContextInitializer<?>>> contextInitializerClasses, @Nullable Set<Class<? extends ApplicationContextInitializer<?>>> contextInitializerClasses,
@Nullable String[] activeProfiles, @Nullable String[] propertySourceLocations, @Nullable String[] activeProfiles, @Nullable String[] propertySourceLocations,
@ -233,10 +241,13 @@ public class MergedContextConfiguration implements Serializable {
* @param contextCustomizers the context customizers * @param contextCustomizers the context customizers
* @param contextLoader the resolved {@code ContextLoader} * @param contextLoader the resolved {@code ContextLoader}
* @param cacheAwareContextLoaderDelegate a cache-aware context loader * @param cacheAwareContextLoaderDelegate a cache-aware context loader
* delegate with which to retrieve the parent context * delegate with which to retrieve the parent {@code ApplicationContext}
* @param parent the parent configuration or {@code null} if there is no parent * @param parent the parent configuration or {@code null} if there is no parent
* @since 4.3 * @since 4.3
* @deprecated since 6.1 in favor of
* {@link #MergedContextConfiguration(Class, String[], Class[], Set, String[], List, String[], Set, ContextLoader, CacheAwareContextLoaderDelegate, MergedContextConfiguration)}
*/ */
@Deprecated(since = "6.1")
public MergedContextConfiguration(Class<?> testClass, @Nullable String[] locations, @Nullable Class<?>[] classes, public MergedContextConfiguration(Class<?> testClass, @Nullable String[] locations, @Nullable Class<?>[] classes,
@Nullable Set<Class<? extends ApplicationContextInitializer<?>>> contextInitializerClasses, @Nullable Set<Class<? extends ApplicationContextInitializer<?>>> contextInitializerClasses,
@Nullable String[] activeProfiles, @Nullable String[] propertySourceLocations, @Nullable String[] activeProfiles, @Nullable String[] propertySourceLocations,
@ -244,12 +255,52 @@ public class MergedContextConfiguration implements Serializable {
ContextLoader contextLoader, @Nullable CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate, ContextLoader contextLoader, @Nullable CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate,
@Nullable MergedContextConfiguration parent) { @Nullable MergedContextConfiguration parent) {
this(testClass, locations, classes, contextInitializerClasses, activeProfiles,
List.of(new PropertySourceDescriptor(processStrings(propertySourceLocations))),
propertySourceProperties, contextCustomizers, contextLoader, cacheAwareContextLoaderDelegate,
parent);
}
/**
* Create a new {@code MergedContextConfiguration} instance for the supplied
* parameters.
* <p>If a {@code null} value is supplied for {@code locations}, {@code classes},
* {@code activeProfiles}, or {@code propertySourceProperties} an empty array
* will be stored instead. If a {@code null} value is supplied for
* {@code contextInitializerClasses} or {@code contextCustomizers}, an empty
* set will be stored instead. Furthermore, active profiles will be sorted,
* and duplicate profiles will be removed.
* @param testClass the test class for which the configuration was merged
* @param locations the merged context resource locations
* @param classes the merged annotated classes
* @param contextInitializerClasses the merged context initializer classes
* @param activeProfiles the merged active bean definition profiles
* @param propertySourceDescriptors the merged property source descriptors
* @param propertySourceProperties the merged inlined properties
* @param contextCustomizers the context customizers
* @param contextLoader the resolved {@code ContextLoader}
* @param cacheAwareContextLoaderDelegate a cache-aware context loader
* delegate with which to retrieve the parent {@code ApplicationContext}
* @param parent the parent configuration or {@code null} if there is no parent
* @since 6.1
*/
public MergedContextConfiguration(Class<?> testClass, @Nullable String[] locations, @Nullable Class<?>[] classes,
@Nullable Set<Class<? extends ApplicationContextInitializer<?>>> contextInitializerClasses,
@Nullable String[] activeProfiles, List<PropertySourceDescriptor> propertySourceDescriptors,
@Nullable String[] propertySourceProperties, @Nullable Set<ContextCustomizer> contextCustomizers,
ContextLoader contextLoader, @Nullable CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate,
@Nullable MergedContextConfiguration parent) {
this.testClass = testClass; this.testClass = testClass;
this.locations = processStrings(locations); this.locations = processStrings(locations);
this.classes = processClasses(classes); this.classes = processClasses(classes);
this.contextInitializerClasses = processContextInitializerClasses(contextInitializerClasses); this.contextInitializerClasses = processContextInitializerClasses(contextInitializerClasses);
this.activeProfiles = processActiveProfiles(activeProfiles); this.activeProfiles = processActiveProfiles(activeProfiles);
this.propertySourceLocations = processStrings(propertySourceLocations); this.propertySourceDescriptors = Collections.unmodifiableList(propertySourceDescriptors);
this.propertySourceLocations = this.propertySourceDescriptors.stream()
.map(PropertySourceDescriptor::locations)
.flatMap(List::stream)
.toArray(String[]::new);
this.propertySourceProperties = processStrings(propertySourceProperties); this.propertySourceProperties = processStrings(propertySourceProperties);
this.contextCustomizers = processContextCustomizers(contextCustomizers); this.contextCustomizers = processContextCustomizers(contextCustomizers);
this.contextLoader = contextLoader; this.contextLoader = contextLoader;
@ -338,18 +389,32 @@ public class MergedContextConfiguration implements Serializable {
} }
/** /**
* Get the merged resource locations for test {@code PropertySources} * Get the merged descriptors for resource locations for test {@code PropertySources}
* for the {@linkplain #getTestClass() test class}. * for the {@linkplain #getTestClass() test class}.
* <p>Properties will be loaded into the {@code Environment}'s set of
* {@code PropertySources}.
* @since 6.1
* @see TestPropertySource#locations
* @see TestPropertySource#factory
*/
public List<PropertySourceDescriptor> getPropertySourceDescriptors() {
return this.propertySourceDescriptors;
}
/**
* Get the merged resource locations of properties files for the
* {@linkplain #getTestClass() test class}.
* @see TestPropertySource#locations * @see TestPropertySource#locations
* @see java.util.Properties * @see java.util.Properties
* @deprecated since 6.1 in favor of {@link #getPropertySourceDescriptors()}
*/ */
@Deprecated(since = "6.1")
public String[] getPropertySourceLocations() { public String[] getPropertySourceLocations() {
return this.propertySourceLocations; return this.propertySourceLocations;
} }
/** /**
* Get the merged test {@code PropertySource} properties for the * Get the merged inlined properties for the {@linkplain #getTestClass() test class}.
* {@linkplain #getTestClass() test class}.
* <p>Properties will be loaded into the {@code Environment}'s set of * <p>Properties will be loaded into the {@code Environment}'s set of
* {@code PropertySources}. * {@code PropertySources}.
* @see TestPropertySource#properties * @see TestPropertySource#properties
@ -408,12 +473,13 @@ public class MergedContextConfiguration implements Serializable {
/** /**
* Determine if the supplied object is equal to this {@code MergedContextConfiguration} * Determine if the supplied object is equal to this {@code MergedContextConfiguration}
* instance by comparing both object's {@linkplain #getLocations() locations}, * instance by comparing both objects' {@linkplain #getLocations() locations},
* {@linkplain #getClasses() annotated classes}, * {@linkplain #getClasses() annotated classes},
* {@linkplain #getContextInitializerClasses() context initializer classes}, * {@linkplain #getContextInitializerClasses() context initializer classes},
* {@linkplain #getActiveProfiles() active profiles}, * {@linkplain #getActiveProfiles() active profiles},
* {@linkplain #getPropertySourceLocations() property source locations}, * {@linkplain #getPropertySourceDescriptors() property source descriptors},
* {@linkplain #getPropertySourceProperties() property source properties}, * {@linkplain #getPropertySourceProperties() property source properties},
* {@linkplain #getContextCustomizers() context customizers},
* {@linkplain #getParent() parents}, and the fully qualified names of their * {@linkplain #getParent() parents}, and the fully qualified names of their
* {@link #getContextLoader() ContextLoaders}. * {@link #getContextLoader() ContextLoaders}.
*/ */
@ -439,7 +505,7 @@ public class MergedContextConfiguration implements Serializable {
if (!Arrays.equals(this.activeProfiles, otherConfig.activeProfiles)) { if (!Arrays.equals(this.activeProfiles, otherConfig.activeProfiles)) {
return false; return false;
} }
if (!Arrays.equals(this.propertySourceLocations, otherConfig.propertySourceLocations)) { if (!this.propertySourceDescriptors.equals(otherConfig.propertySourceDescriptors)) {
return false; return false;
} }
if (!Arrays.equals(this.propertySourceProperties, otherConfig.propertySourceProperties)) { if (!Arrays.equals(this.propertySourceProperties, otherConfig.propertySourceProperties)) {
@ -476,7 +542,7 @@ public class MergedContextConfiguration implements Serializable {
result = 31 * result + Arrays.hashCode(this.classes); result = 31 * result + Arrays.hashCode(this.classes);
result = 31 * result + this.contextInitializerClasses.hashCode(); result = 31 * result + this.contextInitializerClasses.hashCode();
result = 31 * result + Arrays.hashCode(this.activeProfiles); result = 31 * result + Arrays.hashCode(this.activeProfiles);
result = 31 * result + Arrays.hashCode(this.propertySourceLocations); result = 31 * result + this.propertySourceDescriptors.hashCode();
result = 31 * result + Arrays.hashCode(this.propertySourceProperties); result = 31 * result + Arrays.hashCode(this.propertySourceProperties);
result = 31 * result + this.contextCustomizers.hashCode(); result = 31 * result + this.contextCustomizers.hashCode();
result = 31 * result + (this.parent != null ? this.parent.hashCode() : 0); result = 31 * result + (this.parent != null ? this.parent.hashCode() : 0);
@ -489,7 +555,7 @@ public class MergedContextConfiguration implements Serializable {
* {@linkplain #getLocations() locations}, {@linkplain #getClasses() annotated classes}, * {@linkplain #getLocations() locations}, {@linkplain #getClasses() annotated classes},
* {@linkplain #getContextInitializerClasses() context initializer classes}, * {@linkplain #getContextInitializerClasses() context initializer classes},
* {@linkplain #getActiveProfiles() active profiles}, * {@linkplain #getActiveProfiles() active profiles},
* {@linkplain #getPropertySourceLocations() property source locations}, * {@linkplain #getPropertySourceDescriptors() property source descriptors},
* {@linkplain #getPropertySourceProperties() property source properties}, * {@linkplain #getPropertySourceProperties() property source properties},
* {@linkplain #getContextCustomizers() context customizers}, * {@linkplain #getContextCustomizers() context customizers},
* the name of the {@link #getContextLoader() ContextLoader}, and the * the name of the {@link #getContextLoader() ContextLoader}, and the
@ -503,7 +569,7 @@ public class MergedContextConfiguration implements Serializable {
.append("classes", this.classes) .append("classes", this.classes)
.append("contextInitializerClasses", this.contextInitializerClasses) .append("contextInitializerClasses", this.contextInitializerClasses)
.append("activeProfiles", this.activeProfiles) .append("activeProfiles", this.activeProfiles)
.append("propertySourceLocations", this.propertySourceLocations) .append("propertySourceDescriptors", this.propertySourceDescriptors)
.append("propertySourceProperties", this.propertySourceProperties) .append("propertySourceProperties", this.propertySourceProperties)
.append("contextCustomizers", this.contextCustomizers) .append("contextCustomizers", this.contextCustomizers)
.append("contextLoader", (this.contextLoader != null ? this.contextLoader.getClass() : null)) .append("contextLoader", (this.contextLoader != null ? this.contextLoader.getClass() : null))
@ -512,7 +578,7 @@ public class MergedContextConfiguration implements Serializable {
} }
private static String[] processStrings(@Nullable String[] array) { protected static String[] processStrings(@Nullable String[] array) {
return (array != null ? array : EMPTY_STRING_ARRAY); return (array != null ? array : EMPTY_STRING_ARRAY);
} }

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2022 the original author or authors. * Copyright 2002-2023 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -25,6 +25,7 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; import java.lang.annotation.Target;
import org.springframework.core.annotation.AliasFor; import org.springframework.core.annotation.AliasFor;
import org.springframework.core.io.support.PropertySourceFactory;
/** /**
* {@code @TestPropertySource} is a class-level annotation that is used to * {@code @TestPropertySource} is a class-level annotation that is used to
@ -113,9 +114,10 @@ public @interface TestPropertySource {
* will be added to the enclosing {@code Environment} as its own property * will be added to the enclosing {@code Environment} as its own property
* source, in the order declared. * source, in the order declared.
* <h4>Supported File Formats</h4> * <h4>Supported File Formats</h4>
* <p>Both traditional and XML-based properties file formats are supported * <p>By default, both traditional and XML-based properties file formats are
* &mdash; for example, {@code "classpath:/com/example/test.properties"} * supported &mdash; for example, {@code "classpath:/com/example/test.properties"}
* or {@code "file:/path/to/file.xml"}. * or {@code "file:/path/to/file.xml"}. To support a different file format,
* configure an appropriate {@link #factory() PropertySourceFactory}.
* <h4>Path Resource Semantics</h4> * <h4>Path Resource Semantics</h4>
* <p>Each path will be interpreted as a Spring * <p>Each path will be interpreted as a Spring
* {@link org.springframework.core.io.Resource Resource}. A plain path * {@link org.springframework.core.io.Resource Resource}. A plain path
@ -129,9 +131,8 @@ public @interface TestPropertySource {
* {@link org.springframework.util.ResourceUtils#FILE_URL_PREFIX file:}, * {@link org.springframework.util.ResourceUtils#FILE_URL_PREFIX file:},
* {@code http:}, etc.) will be loaded using the specified resource protocol. * {@code http:}, etc.) will be loaded using the specified resource protocol.
* Resource location wildcards (e.g. <code>*&#42;/*.properties</code>) * Resource location wildcards (e.g. <code>*&#42;/*.properties</code>)
* are not permitted: each location must evaluate to exactly one * are not permitted: each location must evaluate to exactly one properties
* {@code .properties} or {@code .xml} resource. Property placeholders * resource. Property placeholders in paths (i.e., <code>${...}</code>) will be
* in paths (i.e., <code>${...}</code>) will be
* {@linkplain org.springframework.core.env.Environment#resolveRequiredPlaceholders(String) resolved} * {@linkplain org.springframework.core.env.Environment#resolveRequiredPlaceholders(String) resolved}
* against the {@code Environment}. * against the {@code Environment}.
* <h4>Default Properties File Detection</h4> * <h4>Default Properties File Detection</h4>
@ -144,6 +145,7 @@ public @interface TestPropertySource {
* @see #inheritLocations * @see #inheritLocations
* @see #value * @see #value
* @see #properties * @see #properties
* @see #factory
* @see org.springframework.core.env.PropertySource * @see org.springframework.core.env.PropertySource
*/ */
@AliasFor("value") @AliasFor("value")
@ -278,4 +280,15 @@ public @interface TestPropertySource {
*/ */
boolean inheritProperties() default true; boolean inheritProperties() default true;
/**
* Specify a custom {@link PropertySourceFactory}, if any.
* <p>By default, a factory for standard resource files will be used which
* supports {@code *.properties} and {@code *.xml} file formats for
* {@link java.util.Properties}.
* @since 6.1
* @see org.springframework.core.io.support.DefaultPropertySourceFactory
* @see org.springframework.core.io.support.ResourcePropertySource
*/
Class<? extends PropertySourceFactory> factory() default PropertySourceFactory.class;
} }

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2022 the original author or authors. * Copyright 2002-2023 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -53,6 +53,7 @@ class MergedContextConfigurationRuntimeHints {
private static final Method getResourceBasePathMethod = loadGetResourceBasePathMethod(); private static final Method getResourceBasePathMethod = loadGetResourceBasePathMethod();
@SuppressWarnings("deprecation")
public void registerHints(RuntimeHints runtimeHints, MergedContextConfiguration mergedConfig, ClassLoader classLoader) { public void registerHints(RuntimeHints runtimeHints, MergedContextConfiguration mergedConfig, ClassLoader classLoader) {
// @ContextConfiguration(loader = ...) // @ContextConfiguration(loader = ...)
ContextLoader contextLoader = mergedConfig.getContextLoader(); ContextLoader contextLoader = mergedConfig.getContextLoader();

View File

@ -127,15 +127,15 @@ public abstract class AbstractContextLoader implements SmartContextLoader {
* @param context the newly created application context * @param context the newly created application context
* @param mergedConfig the merged context configuration * @param mergedConfig the merged context configuration
* @since 3.2 * @since 3.2
* @see TestPropertySourceUtils#addPropertiesFilesToEnvironment * @see TestPropertySourceUtils#addPropertySourcesToEnvironment(ConfigurableApplicationContext, List)
* @see TestPropertySourceUtils#addInlinedPropertiesToEnvironment * @see TestPropertySourceUtils#addInlinedPropertiesToEnvironment(ConfigurableApplicationContext, String...)
* @see ApplicationContextInitializer#initialize(ConfigurableApplicationContext) * @see ApplicationContextInitializer#initialize(ConfigurableApplicationContext)
* @see #loadContext(MergedContextConfiguration) * @see #loadContext(MergedContextConfiguration)
* @see ConfigurableApplicationContext#setId * @see ConfigurableApplicationContext#setId
*/ */
protected void prepareContext(ConfigurableApplicationContext context, MergedContextConfiguration mergedConfig) { protected void prepareContext(ConfigurableApplicationContext context, MergedContextConfiguration mergedConfig) {
context.getEnvironment().setActiveProfiles(mergedConfig.getActiveProfiles()); context.getEnvironment().setActiveProfiles(mergedConfig.getActiveProfiles());
TestPropertySourceUtils.addPropertiesFilesToEnvironment(context, mergedConfig.getPropertySourceLocations()); TestPropertySourceUtils.addPropertySourcesToEnvironment(context, mergedConfig.getPropertySourceDescriptors());
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(context, mergedConfig.getPropertySourceProperties()); TestPropertySourceUtils.addInlinedPropertiesToEnvironment(context, mergedConfig.getPropertySourceProperties());
invokeApplicationContextInitializers(context, mergedConfig); invokeApplicationContextInitializers(context, mergedConfig);
} }

View File

@ -368,7 +368,7 @@ public abstract class AbstractTestContextBootstrapper implements TestContextBoot
StringUtils.toStringArray(locations), ClassUtils.toClassArray(classes), StringUtils.toStringArray(locations), ClassUtils.toClassArray(classes),
ApplicationContextInitializerUtils.resolveInitializerClasses(configAttributesList), ApplicationContextInitializerUtils.resolveInitializerClasses(configAttributesList),
ActiveProfilesUtils.resolveActiveProfiles(testClass), ActiveProfilesUtils.resolveActiveProfiles(testClass),
mergedTestPropertySources.getLocations(), mergedTestPropertySources.getPropertySourceDescriptors(),
mergedTestPropertySources.getProperties(), mergedTestPropertySources.getProperties(),
contextCustomizers, contextLoader, cacheAwareContextLoaderDelegate, parentConfig); contextCustomizers, contextLoader, cacheAwareContextLoaderDelegate, parentConfig);

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2022 the original author or authors. * Copyright 2002-2023 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -17,7 +17,9 @@
package org.springframework.test.context.support; package org.springframework.test.context.support;
import java.util.Arrays; import java.util.Arrays;
import java.util.List;
import org.springframework.core.io.support.PropertySourceDescriptor;
import org.springframework.core.style.DefaultToStringStyler; import org.springframework.core.style.DefaultToStringStyler;
import org.springframework.core.style.SimpleValueStyler; import org.springframework.core.style.SimpleValueStyler;
import org.springframework.core.style.ToStringCreator; import org.springframework.core.style.ToStringCreator;
@ -36,9 +38,9 @@ import org.springframework.util.Assert;
*/ */
class MergedTestPropertySources { class MergedTestPropertySources {
private static final MergedTestPropertySources empty = new MergedTestPropertySources(new String[0], new String[0]); private static final MergedTestPropertySources empty = new MergedTestPropertySources(List.of(), new String[0]);
private final String[] locations; private final List<PropertySourceDescriptor> descriptors;
private final String[] properties; private final String[] properties;
@ -53,25 +55,25 @@ class MergedTestPropertySources {
/** /**
* Create a {@code MergedTestPropertySources} instance with the supplied * Create a {@code MergedTestPropertySources} instance with the supplied
* {@code locations} and {@code properties}. * {@code descriptors} and {@code properties}.
* @param locations the resource locations of properties files; may be * @param descriptors the descriptors for resource locations
* empty but never {@code null} * of properties files; may be empty but never {@code null}
* @param properties the properties in the form of {@code key=value} pairs; * @param properties the properties in the form of {@code key=value} pairs;
* may be empty but never {@code null} * may be empty but never {@code null}
*/ */
MergedTestPropertySources(String[] locations, String[] properties) { MergedTestPropertySources(List<PropertySourceDescriptor> descriptors, String[] properties) {
Assert.notNull(locations, "The locations array must not be null"); Assert.notNull(descriptors, "The descriptors list must not be null");
Assert.notNull(properties, "The properties array must not be null"); Assert.notNull(properties, "The properties array must not be null");
this.locations = locations; this.descriptors = descriptors;
this.properties = properties; this.properties = properties;
} }
/** /**
* Get the resource locations of properties files. * Get the descriptors for resource locations of properties files.
* @see TestPropertySource#locations() * @see TestPropertySource#locations()
*/ */
String[] getLocations() { List<PropertySourceDescriptor> getPropertySourceDescriptors() {
return this.locations; return this.descriptors;
} }
/** /**
@ -84,8 +86,8 @@ class MergedTestPropertySources {
/** /**
* Determine if the supplied object is equal to this {@code MergedTestPropertySources} * Determine if the supplied object is equal to this {@code MergedTestPropertySources}
* instance by comparing both object's {@linkplain #getLocations() locations} * instance by comparing both objects' {@linkplain #getPropertySourceDescriptors()
* and {@linkplain #getProperties() properties}. * descriptors} and {@linkplain #getProperties() properties}.
* @since 5.3 * @since 5.3
*/ */
@Override @Override
@ -98,7 +100,7 @@ class MergedTestPropertySources {
} }
MergedTestPropertySources that = (MergedTestPropertySources) other; MergedTestPropertySources that = (MergedTestPropertySources) other;
if (!Arrays.equals(this.locations, that.locations)) { if (!this.descriptors.equals(that.descriptors)) {
return false; return false;
} }
if (!Arrays.equals(this.properties, that.properties)) { if (!Arrays.equals(this.properties, that.properties)) {
@ -115,7 +117,7 @@ class MergedTestPropertySources {
*/ */
@Override @Override
public int hashCode() { public int hashCode() {
int result = Arrays.hashCode(this.locations); int result = this.descriptors.hashCode();
result = 31 * result + Arrays.hashCode(this.properties); result = 31 * result + Arrays.hashCode(this.properties);
return result; return result;
} }
@ -128,7 +130,7 @@ class MergedTestPropertySources {
@Override @Override
public String toString() { public String toString() {
return new ToStringCreator(this, new DefaultToStringStyler(new SimpleValueStyler())) return new ToStringCreator(this, new DefaultToStringStyler(new SimpleValueStyler()))
.append("locations", this.locations) .append("descriptors", this.descriptors)
.append("properties", this.properties) .append("properties", this.properties)
.toString(); .toString();
} }

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2022 the original author or authors. * Copyright 2002-2023 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -25,12 +25,15 @@ import org.apache.commons.logging.LogFactory;
import org.springframework.core.annotation.MergedAnnotation; import org.springframework.core.annotation.MergedAnnotation;
import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.support.PropertySourceDescriptor;
import org.springframework.core.io.support.PropertySourceFactory;
import org.springframework.core.log.LogMessage; import org.springframework.core.log.LogMessage;
import org.springframework.core.style.DefaultToStringStyler; import org.springframework.core.style.DefaultToStringStyler;
import org.springframework.core.style.SimpleValueStyler; import org.springframework.core.style.SimpleValueStyler;
import org.springframework.core.style.ToStringCreator; import org.springframework.core.style.ToStringCreator;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
import org.springframework.test.context.TestPropertySource; import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.util.TestContextResourceUtils;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.ClassUtils; import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils; import org.springframework.util.ObjectUtils;
@ -61,7 +64,7 @@ class TestPropertySourceAttributes {
private final MergedAnnotation<?> rootAnnotation; private final MergedAnnotation<?> rootAnnotation;
private final List<String> locations = new ArrayList<>(); private final List<PropertySourceDescriptor> descriptors = new ArrayList<>();
private final boolean inheritLocations; private final boolean inheritLocations;
@ -70,12 +73,13 @@ class TestPropertySourceAttributes {
private final boolean inheritProperties; private final boolean inheritProperties;
TestPropertySourceAttributes(MergedAnnotation<TestPropertySource> annotation) { @SuppressWarnings("unchecked")
this.declaringClass = declaringClass(annotation); TestPropertySourceAttributes(MergedAnnotation<TestPropertySource> mergedAnnotation) {
this.rootAnnotation = annotation.getRoot(); this.declaringClass = declaringClass(mergedAnnotation);
this.inheritLocations = annotation.getBoolean("inheritLocations"); this.rootAnnotation = mergedAnnotation.getRoot();
this.inheritProperties = annotation.getBoolean("inheritProperties"); this.inheritLocations = mergedAnnotation.getBoolean("inheritLocations");
addPropertiesAndLocationsFrom(annotation); this.inheritProperties = mergedAnnotation.getBoolean("inheritProperties");
addPropertiesAndLocationsFrom(mergedAnnotation, this.declaringClass);
} }
/** /**
@ -112,29 +116,52 @@ class TestPropertySourceAttributes {
attributeName)); attributeName));
} }
private void addPropertiesAndLocationsFrom(MergedAnnotation<TestPropertySource> mergedAnnotation) { @SuppressWarnings("unchecked")
private void addPropertiesAndLocationsFrom(MergedAnnotation<TestPropertySource> mergedAnnotation,
Class<?> declaringClass) {
String[] locations = mergedAnnotation.getStringArray("locations"); String[] locations = mergedAnnotation.getStringArray("locations");
String[] properties = mergedAnnotation.getStringArray("properties"); String[] properties = mergedAnnotation.getStringArray("properties");
addPropertiesAndLocations(locations, properties, declaringClass(mergedAnnotation), false); String[] convertedLocations =
TestContextResourceUtils.convertToClasspathResourcePaths(declaringClass, true, locations);
Class<? extends PropertySourceFactory> factoryClass =
(Class<? extends PropertySourceFactory>) mergedAnnotation.getClass("factory");
PropertySourceDescriptor descriptor = new PropertySourceDescriptor(
Arrays.asList(convertedLocations), false, null, factoryClass, null);
addPropertiesAndLocations(List.of(descriptor), properties, declaringClass, false);
} }
private void mergePropertiesAndLocationsFrom(TestPropertySourceAttributes attributes) { private void mergePropertiesAndLocationsFrom(TestPropertySourceAttributes attributes) {
addPropertiesAndLocations(attributes.getLocations(), attributes.getProperties(), addPropertiesAndLocations(attributes.getPropertySourceDescriptors(), attributes.getProperties(),
attributes.getDeclaringClass(), true); attributes.getDeclaringClass(), true);
} }
private void addPropertiesAndLocations(String[] locations, String[] properties, private void addPropertiesAndLocations(List<PropertySourceDescriptor> descriptors, String[] properties,
Class<?> declaringClass, boolean prepend) { Class<?> declaringClass, boolean prepend) {
if (ObjectUtils.isEmpty(locations) && ObjectUtils.isEmpty(properties)) { if (hasNoLocations(descriptors) && ObjectUtils.isEmpty(properties)) {
addAll(prepend, this.locations, detectDefaultPropertiesFile(declaringClass)); String defaultPropertiesFile = detectDefaultPropertiesFile(declaringClass);
addAll(prepend, this.descriptors, List.of(new PropertySourceDescriptor(defaultPropertiesFile)));
} }
else { else {
addAll(prepend, this.locations, locations); addAll(prepend, this.descriptors, descriptors);
addAll(prepend, this.properties, properties); addAll(prepend, this.properties, properties);
} }
} }
/**
* Add all the supplied elements to the provided list, honoring the
* {@code prepend} flag.
* <p>If the {@code prepend} flag is {@code false}, the elements will be appended
* to the list.
* @param prepend whether the elements should be prepended to the list
* @param list the list to which to add the elements
* @param elements the elements to add to the list
*/
private void addAll(boolean prepend, List<PropertySourceDescriptor> list, List<PropertySourceDescriptor> elements) {
list.addAll((prepend ? 0 : list.size()), elements);
}
/** /**
* Add all the supplied elements to the provided list, honoring the * Add all the supplied elements to the provided list, honoring the
* {@code prepend} flag. * {@code prepend} flag.
@ -177,16 +204,18 @@ class TestPropertySourceAttributes {
} }
/** /**
* Get the resource locations that were declared via {@code @TestPropertySource}. * Get the descriptors for resource locations that were declared via
* {@code @TestPropertySource}.
* <p>Note: The returned value may represent a <em>detected default</em> * <p>Note: The returned value may represent a <em>detected default</em>
* or merged locations that do not match the original value declared via a * or merged locations that do not match the original value declared via a
* single {@code @TestPropertySource} annotation. * single {@code @TestPropertySource} annotation.
* @return the resource locations; potentially <em>empty</em> * @return the resource location descriptors; potentially <em>empty</em>
* @see TestPropertySource#value * @see TestPropertySource#value
* @see TestPropertySource#locations * @see TestPropertySource#locations
* @see TestPropertySource#factory
*/ */
String[] getLocations() { List<PropertySourceDescriptor> getPropertySourceDescriptors() {
return StringUtils.toStringArray(this.locations); return this.descriptors;
} }
/** /**
@ -220,7 +249,7 @@ class TestPropertySourceAttributes {
} }
boolean isEmpty() { boolean isEmpty() {
return (this.locations.isEmpty() && this.properties.isEmpty()); return (hasNoLocations(this.descriptors) && this.properties.isEmpty());
} }
@Override @Override
@ -233,7 +262,7 @@ class TestPropertySourceAttributes {
} }
TestPropertySourceAttributes that = (TestPropertySourceAttributes) other; TestPropertySourceAttributes that = (TestPropertySourceAttributes) other;
if (!this.locations.equals(that.locations)) { if (!this.descriptors.equals(that.descriptors)) {
return false; return false;
} }
if (!this.properties.equals(that.properties)) { if (!this.properties.equals(that.properties)) {
@ -251,7 +280,7 @@ class TestPropertySourceAttributes {
@Override @Override
public int hashCode() { public int hashCode() {
int result = this.locations.hashCode(); int result = this.descriptors.hashCode();
result = 31 * result + this.properties.hashCode(); result = 31 * result + this.properties.hashCode();
result = 31 * result + (this.inheritLocations ? 1231 : 1237); result = 31 * result + (this.inheritLocations ? 1231 : 1237);
result = 31 * result + (this.inheritProperties ? 1231 : 1237); result = 31 * result + (this.inheritProperties ? 1231 : 1237);
@ -265,8 +294,8 @@ class TestPropertySourceAttributes {
@Override @Override
public String toString() { public String toString() {
return new ToStringCreator(this, new DefaultToStringStyler(new SimpleValueStyler())) return new ToStringCreator(this, new DefaultToStringStyler(new SimpleValueStyler()))
.append("declaringClass", this.declaringClass) .append("declaringClass", this.declaringClass.getName())
.append("locations", this.locations) .append("descriptors", this.descriptors)
.append("inheritLocations", this.inheritLocations) .append("inheritLocations", this.inheritLocations)
.append("properties", this.properties) .append("properties", this.properties)
.append("inheritProperties", this.inheritProperties) .append("inheritProperties", this.inheritProperties)
@ -279,4 +308,12 @@ class TestPropertySourceAttributes {
return (Class<?>) source; return (Class<?>) source;
} }
/**
* Determine if the supplied list contains no descriptor with locations.
*/
private static boolean hasNoLocations(List<PropertySourceDescriptor> descriptors) {
return descriptors.stream().map(PropertySourceDescriptor::locations)
.flatMap(List::stream).findAny().isEmpty();
}
} }

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2022 the original author or authors. * Copyright 2002-2023 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -30,6 +30,7 @@ import java.util.Properties;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.annotation.MergedAnnotation; import org.springframework.core.annotation.MergedAnnotation;
import org.springframework.core.annotation.MergedAnnotations; import org.springframework.core.annotation.MergedAnnotations;
@ -37,15 +38,18 @@ import org.springframework.core.annotation.MergedAnnotations.SearchStrategy;
import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment; import org.springframework.core.env.Environment;
import org.springframework.core.env.MapPropertySource; import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.PropertySource; import org.springframework.core.env.PropertySource;
import org.springframework.core.env.PropertySources; import org.springframework.core.env.PropertySources;
import org.springframework.core.io.Resource; import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader; import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.support.ResourcePropertySource; import org.springframework.core.io.support.DefaultPropertySourceFactory;
import org.springframework.core.io.support.EncodedResource;
import org.springframework.core.io.support.PropertySourceDescriptor;
import org.springframework.core.io.support.PropertySourceFactory;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
import org.springframework.test.context.TestContextAnnotationUtils; import org.springframework.test.context.TestContextAnnotationUtils;
import org.springframework.test.context.TestPropertySource; import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.util.TestContextResourceUtils;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils; import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
@ -71,6 +75,8 @@ public abstract class TestPropertySourceUtils {
*/ */
public static final String INLINED_PROPERTIES_PROPERTY_SOURCE_NAME = "Inlined Test Properties"; public static final String INLINED_PROPERTIES_PROPERTY_SOURCE_NAME = "Inlined Test Properties";
private static final PropertySourceFactory defaultPropertySourceFactory = new DefaultPropertySourceFactory();
private static final Log logger = LogFactory.getLog(TestPropertySourceUtils.class); private static final Log logger = LogFactory.getLog(TestPropertySourceUtils.class);
@ -139,20 +145,18 @@ public abstract class TestPropertySourceUtils {
return duplicationDetected; return duplicationDetected;
} }
private static String[] mergeLocations(List<TestPropertySourceAttributes> attributesList) { private static List<PropertySourceDescriptor> mergeLocations(List<TestPropertySourceAttributes> attributesList) {
List<String> locations = new ArrayList<>(); List<PropertySourceDescriptor> descriptors = new ArrayList<>();
for (TestPropertySourceAttributes attrs : attributesList) { for (TestPropertySourceAttributes attrs : attributesList) {
if (logger.isTraceEnabled()) { if (logger.isTraceEnabled()) {
logger.trace("Processing locations for " + attrs); logger.trace("Processing locations for " + attrs);
} }
String[] locationsArray = TestContextResourceUtils.convertToClasspathResourcePaths( descriptors.addAll(0, attrs.getPropertySourceDescriptors());
attrs.getDeclaringClass(), true, attrs.getLocations());
locations.addAll(0, Arrays.asList(locationsArray));
if (!attrs.isInheritLocations()) { if (!attrs.isInheritLocations()) {
break; break;
} }
} }
return StringUtils.toStringArray(locations); return descriptors;
} }
private static String[] mergeProperties(List<TestPropertySourceAttributes> attributesList) { private static String[] mergeProperties(List<TestPropertySourceAttributes> attributesList) {
@ -181,9 +185,10 @@ public abstract class TestPropertySourceUtils {
* to the environment; potentially empty but never {@code null} * to the environment; potentially empty but never {@code null}
* @throws IllegalStateException if an error occurs while processing a properties file * @throws IllegalStateException if an error occurs while processing a properties file
* @since 4.1.5 * @since 4.1.5
* @see ResourcePropertySource * @see org.springframework.core.io.support.ResourcePropertySource
* @see TestPropertySource#locations * @see TestPropertySource#locations
* @see #addPropertiesFilesToEnvironment(ConfigurableEnvironment, ResourceLoader, String...) * @see #addPropertiesFilesToEnvironment(ConfigurableEnvironment, ResourceLoader, String...)
* @see #addPropertySourcesToEnvironment(ConfigurableApplicationContext, List)
*/ */
public static void addPropertiesFilesToEnvironment(ConfigurableApplicationContext context, String... locations) { public static void addPropertiesFilesToEnvironment(ConfigurableApplicationContext context, String... locations) {
Assert.notNull(context, "'context' must not be null"); Assert.notNull(context, "'context' must not be null");
@ -197,7 +202,8 @@ public abstract class TestPropertySourceUtils {
* <p>Property placeholders in resource locations (i.e., <code>${...}</code>) * <p>Property placeholders in resource locations (i.e., <code>${...}</code>)
* will be {@linkplain Environment#resolveRequiredPlaceholders(String) resolved} * will be {@linkplain Environment#resolveRequiredPlaceholders(String) resolved}
* against the {@code Environment}. * against the {@code Environment}.
* <p>Each properties file will be converted to a {@link ResourcePropertySource} * <p>Each properties file will be converted to a
* {@link org.springframework.core.io.support.ResourcePropertySource ResourcePropertySource}
* that will be added to the {@link PropertySources} of the environment with * that will be added to the {@link PropertySources} of the environment with
* the highest precedence. * the highest precedence.
* @param environment the environment to update; never {@code null} * @param environment the environment to update; never {@code null}
@ -207,21 +213,95 @@ public abstract class TestPropertySourceUtils {
* to the environment; potentially empty but never {@code null} * to the environment; potentially empty but never {@code null}
* @throws IllegalStateException if an error occurs while processing a properties file * @throws IllegalStateException if an error occurs while processing a properties file
* @since 4.3 * @since 4.3
* @see ResourcePropertySource * @see org.springframework.core.io.support.ResourcePropertySource
* @see TestPropertySource#locations * @see TestPropertySource#locations
* @see #addPropertiesFilesToEnvironment(ConfigurableApplicationContext, String...) * @see #addPropertiesFilesToEnvironment(ConfigurableApplicationContext, String...)
* @see #addPropertySourcesToEnvironment(ConfigurableApplicationContext, List)
*/ */
public static void addPropertiesFilesToEnvironment(ConfigurableEnvironment environment, public static void addPropertiesFilesToEnvironment(ConfigurableEnvironment environment,
ResourceLoader resourceLoader, String... locations) { ResourceLoader resourceLoader, String... locations) {
Assert.notNull(locations, "'locations' must not be null");
addPropertySourcesToEnvironment(environment, resourceLoader,
List.of(new PropertySourceDescriptor(locations)));
}
/**
* Add property sources for the given {@code descriptors} to the
* {@link Environment} of the supplied {@code context}.
* <p>Property placeholders in resource locations (i.e., <code>${...}</code>)
* will be {@linkplain Environment#resolveRequiredPlaceholders(String) resolved}
* against the {@code Environment}.
* <p>Each {@link PropertySource} will be created via the configured
* {@link PropertySourceDescriptor#propertySourceFactory() PropertySourceFactory}
* (or the {@link DefaultPropertySourceFactory} if no factory is configured)
* and added to the {@link PropertySources} of the environment with the highest
* precedence.
* @param context the application context whose environment should be updated;
* never {@code null}
* @param descriptors the property source descriptors to process; potentially
* empty but never {@code null}
* @throws IllegalStateException if an error occurs while processing the
* descriptors and registering property sources
* @since 6.1
* @see TestPropertySource#locations
* @see TestPropertySource#factory
* @see PropertySourceFactory
*/
public static void addPropertySourcesToEnvironment(ConfigurableApplicationContext context,
List<PropertySourceDescriptor> descriptors) {
Assert.notNull(context, "'context' must not be null");
Assert.notNull(descriptors, "'descriptors' must not be null");
addPropertySourcesToEnvironment(context.getEnvironment(), context, descriptors);
}
/**
* Add property sources for the given {@code descriptors} to the supplied
* {@link ConfigurableEnvironment environment}.
* <p>Property placeholders in resource locations (i.e., <code>${...}</code>)
* will be {@linkplain Environment#resolveRequiredPlaceholders(String) resolved}
* against the {@code Environment}.
* <p>Each {@link PropertySource} will be created via the configured
* {@link PropertySourceDescriptor#propertySourceFactory() PropertySourceFactory}
* (or the {@link DefaultPropertySourceFactory} if no factory is configured)
* and added to the {@link PropertySources} of the environment with the highest
* precedence.
* @param environment the environment to update; never {@code null}
* @param resourceLoader the {@code ResourceLoader} to use to load each resource;
* never {@code null}
* @param descriptors the property source descriptors to process; potentially
* empty but never {@code null}
* @throws IllegalStateException if an error occurs while processing the
* descriptors and registering property sources
* @since 6.1
* @see TestPropertySource#locations
* @see TestPropertySource#factory
* @see PropertySourceFactory
*/
private static void addPropertySourcesToEnvironment(ConfigurableEnvironment environment,
ResourceLoader resourceLoader, List<PropertySourceDescriptor> descriptors) {
Assert.notNull(environment, "'environment' must not be null"); Assert.notNull(environment, "'environment' must not be null");
Assert.notNull(resourceLoader, "'resourceLoader' must not be null"); Assert.notNull(resourceLoader, "'resourceLoader' must not be null");
Assert.notNull(locations, "'locations' must not be null"); Assert.notNull(descriptors, "'descriptors' must not be null");
MutablePropertySources propertySources = environment.getPropertySources();
try { try {
for (String location : locations) { for (PropertySourceDescriptor descriptor : descriptors) {
String resolvedLocation = environment.resolveRequiredPlaceholders(location); if (!descriptor.locations().isEmpty()) {
Resource resource = resourceLoader.getResource(resolvedLocation); Class<? extends PropertySourceFactory> factoryClass = descriptor.propertySourceFactory();
environment.getPropertySources().addFirst(new ResourcePropertySource(resource)); PropertySourceFactory factory =
(factoryClass != null && factoryClass != PropertySourceFactory.class ?
BeanUtils.instantiateClass(factoryClass) : defaultPropertySourceFactory);
for (String location : descriptor.locations()) {
String resolvedLocation = environment.resolveRequiredPlaceholders(location);
Resource resource = resourceLoader.getResource(resolvedLocation);
PropertySource<?> propertySource = factory.createPropertySource(descriptor.name(),
new EncodedResource(resource, descriptor.encoding()));
propertySources.addFirst(propertySource);
}
}
} }
} }
catch (IOException ex) { catch (IOException ex) {

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2022 the original author or authors. * Copyright 2002-2023 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -16,9 +16,11 @@
package org.springframework.test.context.web; package org.springframework.test.context.web;
import java.util.List;
import java.util.Set; import java.util.Set;
import org.springframework.context.ApplicationContextInitializer; import org.springframework.context.ApplicationContextInitializer;
import org.springframework.core.io.support.PropertySourceDescriptor;
import org.springframework.core.style.DefaultToStringStyler; import org.springframework.core.style.DefaultToStringStyler;
import org.springframework.core.style.SimpleValueStyler; import org.springframework.core.style.SimpleValueStyler;
import org.springframework.core.style.ToStringCreator; import org.springframework.core.style.ToStringCreator;
@ -99,7 +101,10 @@ public class WebMergedContextConfiguration extends MergedContextConfiguration {
* delegate with which to retrieve the parent context * delegate with which to retrieve the parent context
* @param parent the parent configuration or {@code null} if there is no parent * @param parent the parent configuration or {@code null} if there is no parent
* @since 4.1 * @since 4.1
* @deprecated since 6.1 in favor of
* {@link #WebMergedContextConfiguration(Class, String[], Class[], Set, String[], List, String[], Set, String, ContextLoader, CacheAwareContextLoaderDelegate, MergedContextConfiguration)}
*/ */
@Deprecated(since = "6.1")
public WebMergedContextConfiguration(Class<?> testClass, @Nullable String[] locations, @Nullable Class<?>[] classes, public WebMergedContextConfiguration(Class<?> testClass, @Nullable String[] locations, @Nullable Class<?>[] classes,
@Nullable Set<Class<? extends ApplicationContextInitializer<?>>> contextInitializerClasses, @Nullable Set<Class<? extends ApplicationContextInitializer<?>>> contextInitializerClasses,
@Nullable String[] activeProfiles, @Nullable String[] propertySourceLocations, @Nullable String[] propertySourceProperties, @Nullable String[] activeProfiles, @Nullable String[] propertySourceLocations, @Nullable String[] propertySourceProperties,
@ -135,14 +140,54 @@ public class WebMergedContextConfiguration extends MergedContextConfiguration {
* delegate with which to retrieve the parent context * delegate with which to retrieve the parent context
* @param parent the parent configuration or {@code null} if there is no parent * @param parent the parent configuration or {@code null} if there is no parent
* @since 4.3 * @since 4.3
* @deprecated since 6.1 in favor of
* {@link #WebMergedContextConfiguration(Class, String[], Class[], Set, String[], List, String[], Set, String, ContextLoader, CacheAwareContextLoaderDelegate, MergedContextConfiguration)}
*/ */
@Deprecated(since = "6.1")
public WebMergedContextConfiguration(Class<?> testClass, @Nullable String[] locations, @Nullable Class<?>[] classes, public WebMergedContextConfiguration(Class<?> testClass, @Nullable String[] locations, @Nullable Class<?>[] classes,
@Nullable Set<Class<? extends ApplicationContextInitializer<?>>> contextInitializerClasses, @Nullable Set<Class<? extends ApplicationContextInitializer<?>>> contextInitializerClasses,
@Nullable String[] activeProfiles, @Nullable String[] propertySourceLocations, @Nullable String[] propertySourceProperties, @Nullable String[] activeProfiles, @Nullable String[] propertySourceLocations, @Nullable String[] propertySourceProperties,
@Nullable Set<ContextCustomizer> contextCustomizers, String resourceBasePath, ContextLoader contextLoader, @Nullable Set<ContextCustomizer> contextCustomizers, String resourceBasePath, ContextLoader contextLoader,
CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate, @Nullable MergedContextConfiguration parent) { CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate, @Nullable MergedContextConfiguration parent) {
super(testClass, locations, classes, contextInitializerClasses, activeProfiles, propertySourceLocations, this(testClass, locations, classes, contextInitializerClasses, activeProfiles,
List.of(new PropertySourceDescriptor(processStrings(propertySourceLocations))),
propertySourceProperties, contextCustomizers, resourceBasePath, contextLoader,
cacheAwareContextLoaderDelegate, parent);
}
/**
* Create a new {@code WebMergedContextConfiguration} instance for the supplied
* parameters.
* <p>If a {@code null} value is supplied for {@code locations}, {@code classes},
* {@code activeProfiles}, or {@code propertySourceProperties} an empty array
* will be stored instead. If a {@code null} value is supplied for
* {@code contextInitializerClasses} or {@code contextCustomizers}, an empty
* set will be stored instead. Furthermore, active profiles will be sorted,
* and duplicate profiles will be removed.
* @param testClass the test class for which the configuration was merged
* @param locations the merged context resource locations
* @param classes the merged annotated classes
* @param contextInitializerClasses the merged context initializer classes
* @param activeProfiles the merged active bean definition profiles
* @param propertySourceDescriptors the merged property source descriptors
* @param propertySourceProperties the merged inlined properties
* @param contextCustomizers the context customizers
* @param resourceBasePath the resource path to the root directory of the web application
* @param contextLoader the resolved {@code ContextLoader}
* @param cacheAwareContextLoaderDelegate a cache-aware context loader
* delegate with which to retrieve the parent {@code ApplicationContext}
* @param parent the parent configuration or {@code null} if there is no parent
* @since 6.1
*/
public WebMergedContextConfiguration(Class<?> testClass, @Nullable String[] locations, @Nullable Class<?>[] classes,
@Nullable Set<Class<? extends ApplicationContextInitializer<?>>> contextInitializerClasses,
@Nullable String[] activeProfiles,
List<PropertySourceDescriptor> propertySourceDescriptors, @Nullable String[] propertySourceProperties,
@Nullable Set<ContextCustomizer> contextCustomizers, String resourceBasePath, ContextLoader contextLoader,
CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate, @Nullable MergedContextConfiguration parent) {
super(testClass, locations, classes, contextInitializerClasses, activeProfiles, propertySourceDescriptors,
propertySourceProperties, contextCustomizers, contextLoader, cacheAwareContextLoaderDelegate, parent); propertySourceProperties, contextCustomizers, contextLoader, cacheAwareContextLoaderDelegate, parent);
this.resourceBasePath = (StringUtils.hasText(resourceBasePath) ? resourceBasePath : ""); this.resourceBasePath = (StringUtils.hasText(resourceBasePath) ? resourceBasePath : "");
@ -160,11 +205,14 @@ public class WebMergedContextConfiguration extends MergedContextConfiguration {
/** /**
* Determine if the supplied object is equal to this {@code WebMergedContextConfiguration} * Determine if the supplied object is equal to this {@code WebMergedContextConfiguration}
* instance by comparing both object's {@linkplain #getLocations() locations}, * instance by comparing both objects' {@linkplain #getLocations() locations},
* {@linkplain #getClasses() annotated classes}, * {@linkplain #getClasses() annotated classes},
* {@linkplain #getContextInitializerClasses() context initializer classes}, * {@linkplain #getContextInitializerClasses() context initializer classes},
* {@linkplain #getActiveProfiles() active profiles}, * {@linkplain #getActiveProfiles() active profiles},
* {@linkplain #getResourceBasePath() resource base path}, * {@linkplain #getResourceBasePath() resource base paths},
* {@linkplain #getPropertySourceDescriptors() property source descriptors},
* {@linkplain #getPropertySourceProperties() property source properties},
* {@linkplain #getContextCustomizers() context customizers},
* {@linkplain #getParent() parents}, and the fully qualified names of their * {@linkplain #getParent() parents}, and the fully qualified names of their
* {@link #getContextLoader() ContextLoaders}. * {@link #getContextLoader() ContextLoaders}.
*/ */
@ -189,7 +237,7 @@ public class WebMergedContextConfiguration extends MergedContextConfiguration {
* {@linkplain #getLocations() locations}, {@linkplain #getClasses() annotated classes}, * {@linkplain #getLocations() locations}, {@linkplain #getClasses() annotated classes},
* {@linkplain #getContextInitializerClasses() context initializer classes}, * {@linkplain #getContextInitializerClasses() context initializer classes},
* {@linkplain #getActiveProfiles() active profiles}, * {@linkplain #getActiveProfiles() active profiles},
* {@linkplain #getPropertySourceLocations() property source locations}, * {@linkplain #getPropertySourceDescriptors() property source descriptors},
* {@linkplain #getPropertySourceProperties() property source properties}, * {@linkplain #getPropertySourceProperties() property source properties},
* {@linkplain #getContextCustomizers() context customizers}, * {@linkplain #getContextCustomizers() context customizers},
* {@linkplain #getResourceBasePath() resource base path}, the name of the * {@linkplain #getResourceBasePath() resource base path}, the name of the
@ -204,7 +252,7 @@ public class WebMergedContextConfiguration extends MergedContextConfiguration {
.append("classes", getClasses()) .append("classes", getClasses())
.append("contextInitializerClasses", getContextInitializerClasses()) .append("contextInitializerClasses", getContextInitializerClasses())
.append("activeProfiles", getActiveProfiles()) .append("activeProfiles", getActiveProfiles())
.append("propertySourceLocations", getPropertySourceLocations()) .append("propertySourceDescriptors", getPropertySourceDescriptors())
.append("propertySourceProperties", getPropertySourceProperties()) .append("propertySourceProperties", getPropertySourceProperties())
.append("contextCustomizers", getContextCustomizers()) .append("contextCustomizers", getContextCustomizers())
.append("resourceBasePath", getResourceBasePath()) .append("resourceBasePath", getResourceBasePath())

View File

@ -18,6 +18,7 @@ package org.springframework.test.context;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.List;
import java.util.Set; import java.util.Set;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@ -409,9 +410,9 @@ class MergedContextConfigurationTests {
void equalsWithSameContextCustomizers() { void equalsWithSameContextCustomizers() {
Set<ContextCustomizer> customizers = Collections.singleton(mock()); Set<ContextCustomizer> customizers = Collections.singleton(mock());
MergedContextConfiguration mergedConfig1 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY, MergedContextConfiguration mergedConfig1 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY,
EMPTY_CLASS_ARRAY, null, EMPTY_STRING_ARRAY, null, null, customizers, loader, null, null); EMPTY_CLASS_ARRAY, null, EMPTY_STRING_ARRAY, List.of(), null, customizers, loader, null, null);
MergedContextConfiguration mergedConfig2 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY, MergedContextConfiguration mergedConfig2 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY,
EMPTY_CLASS_ARRAY, null, EMPTY_STRING_ARRAY, null, null, customizers, loader, null, null); EMPTY_CLASS_ARRAY, null, EMPTY_STRING_ARRAY, List.of(), null, customizers, loader, null, null);
assertThat(mergedConfig2).isEqualTo(mergedConfig1); assertThat(mergedConfig2).isEqualTo(mergedConfig1);
} }
@ -424,9 +425,9 @@ class MergedContextConfigurationTests {
Set<ContextCustomizer> customizers2 = Collections.singleton(mock()); Set<ContextCustomizer> customizers2 = Collections.singleton(mock());
MergedContextConfiguration mergedConfig1 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY, MergedContextConfiguration mergedConfig1 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY,
EMPTY_CLASS_ARRAY, null, EMPTY_STRING_ARRAY, null, null, customizers1, loader, null, null); EMPTY_CLASS_ARRAY, null, EMPTY_STRING_ARRAY, List.of(), null, customizers1, loader, null, null);
MergedContextConfiguration mergedConfig2 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY, MergedContextConfiguration mergedConfig2 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY,
EMPTY_CLASS_ARRAY, null, EMPTY_STRING_ARRAY, null, null, customizers2, loader, null, null); EMPTY_CLASS_ARRAY, null, EMPTY_STRING_ARRAY, List.of(), null, customizers2, loader, null, null);
assertThat(mergedConfig2).isNotEqualTo(mergedConfig1); assertThat(mergedConfig2).isNotEqualTo(mergedConfig1);
assertThat(mergedConfig1).isNotEqualTo(mergedConfig2); assertThat(mergedConfig1).isNotEqualTo(mergedConfig2);
} }

View File

@ -0,0 +1,62 @@
/*
* Copyright 2002-2023 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.test.context.env;
import java.util.Properties;
import org.springframework.beans.factory.config.YamlPropertiesFactoryBean;
import org.springframework.core.env.PropertiesPropertySource;
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;
/**
* Demo {@link PropertySourceFactory} that provides YAML support.
*
* @author Sam Brannen
* @since 6.1
*/
class YamlPropertySourceFactory implements PropertySourceFactory {
@Override
public PropertySource<?> createPropertySource(String name, EncodedResource encodedResource) {
Resource resource = encodedResource.getResource();
if (!StringUtils.hasText(name)) {
name = getNameForResource(resource);
}
YamlPropertiesFactoryBean factoryBean = new YamlPropertiesFactoryBean();
factoryBean.setResources(resource);
factoryBean.afterPropertiesSet();
Properties properties = factoryBean.getObject();
return new PropertiesPropertySource(name, properties);
}
/**
* Return the description for the given Resource; if the description is
* empty, return the class name of the resource plus its identity hash code.
*/
private static String getNameForResource(Resource resource) {
String name = resource.getDescription();
if (!StringUtils.hasText(name)) {
name = resource.getClass().getSimpleName() + "@" + System.identityHashCode(resource);
}
return name;
}
}

View File

@ -0,0 +1,39 @@
/*
* Copyright 2002-2023 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.test.context.env;
import java.lang.annotation.ElementType;
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;
/**
* @author Sam Brannen
* @since 6.1
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@TestPropertySource(factory = YamlPropertySourceFactory.class)
public @interface YamlTestProperties {
@AliasFor(annotation = TestPropertySource.class)
String[] value();
}

View File

@ -0,0 +1,59 @@
/*
* Copyright 2002-2023 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.test.context.env;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.io.support.PropertySourceFactory;
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 TestPropertySource @TestPropertySource} support
* with a custom YAML {@link PropertySourceFactory}.
*
* @author Sam Brannen
* @since 6.1
*/
@SpringJUnitConfig
@YamlTestProperties("test-properties.yaml")
class YamlTestPropertySourceTests {
@ParameterizedTest
@CsvSource(delimiterString = "->", textBlock = """
environments.dev.url -> https://dev.example.com
environments.dev.name -> 'Developer Setup'
environments.prod.url -> https://prod.example.com
environments.prod.name -> 'My Cool App'
""")
void propertyIsAvailableInEnvironment(String property, String value, @Autowired ConfigurableEnvironment env) {
assertThat(env.getProperty(property)).isEqualTo(value);
}
@Configuration
static class Config {
/* no user beans required for these tests */
}
}

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2019 the original author or authors. * Copyright 2002-2023 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -16,15 +16,9 @@
package org.springframework.test.context.env.repeatable; package org.springframework.test.context.env.repeatable;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.test.context.TestPropertySource; import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.env.repeatable.LocalPropertiesFileAndMetaPropertiesFileTests.MetaFileTestProperty;
/** /**
* Integration tests for {@link TestPropertySource @TestPropertySource} as a * Integration tests for {@link TestPropertySource @TestPropertySource} as a
@ -48,15 +42,4 @@ class LocalPropertiesFileAndMetaPropertiesFileTests extends AbstractRepeatableTe
assertEnvironmentValue("key2", "meta file"); assertEnvironmentValue("key2", "meta file");
} }
/**
* Composed annotation that declares a properties file via
* {@link TestPropertySource @TestPropertySource}.
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@TestPropertySource("meta.properties")
@interface MetaFileTestProperty {
}
} }

View File

@ -0,0 +1,43 @@
/*
* Copyright 2002-2023 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.test.context.env.repeatable;
import org.junit.jupiter.api.Test;
import org.springframework.test.context.env.YamlTestProperties;
/**
* Analogous to {@link LocalPropertiesFileAndMetaPropertiesFileTests} except
* that the local file is YAML.
*
* @author Sam Brannen
* @since 6.1
*/
@YamlTestProperties("local.yaml")
@MetaFileTestProperty
class LocalYamlFileAndMetaPropertiesFileTests extends AbstractRepeatableTestPropertySourceTests {
@Test
void test() {
assertEnvironmentValue("key1", "local file");
assertEnvironmentValue("key2", "meta file");
assertEnvironmentValue("environments.dev.url", "https://dev.example.com");
assertEnvironmentValue("environments.dev.name", "Developer Setup");
}
}

View File

@ -0,0 +1,34 @@
/*
* Copyright 2002-2023 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.test.context.env.repeatable;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.test.context.TestPropertySource;
/**
* Composed annotation that declares a properties file via
* {@link TestPropertySource @TestPropertySource}.
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@TestPropertySource("meta.properties")
@interface MetaFileTestProperty {
}

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2022 the original author or authors. * Copyright 2002-2023 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -187,6 +187,7 @@ class BootstrapTestUtilsMergedConfigTests extends AbstractContextConfigurationUt
assertMergedConfigForLocationPaths(RelativeFooXmlLocation.class); assertMergedConfigForLocationPaths(RelativeFooXmlLocation.class);
} }
@SuppressWarnings("deprecation")
private void assertMergedConfigForLocationPaths(Class<?> testClass) { private void assertMergedConfigForLocationPaths(Class<?> testClass) {
MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass); MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass);

View File

@ -18,7 +18,9 @@ package org.springframework.test.context.support;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.stream.Stream;
import org.assertj.core.api.SoftAssertions; import org.assertj.core.api.SoftAssertions;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@ -29,6 +31,7 @@ import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MutablePropertySources; import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.io.ByteArrayResource; import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.ResourceLoader; import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.support.PropertySourceDescriptor;
import org.springframework.mock.env.MockEnvironment; import org.springframework.mock.env.MockEnvironment;
import org.springframework.mock.env.MockPropertySource; import org.springframework.mock.env.MockPropertySource;
import org.springframework.test.context.TestPropertySource; import org.springframework.test.context.TestPropertySource;
@ -295,7 +298,9 @@ class TestPropertySourceUtilsTests {
MergedTestPropertySources mergedPropertySources = buildMergedTestPropertySources(testClass); MergedTestPropertySources mergedPropertySources = buildMergedTestPropertySources(testClass);
SoftAssertions.assertSoftly(softly -> { SoftAssertions.assertSoftly(softly -> {
softly.assertThat(mergedPropertySources).isNotNull(); softly.assertThat(mergedPropertySources).isNotNull();
softly.assertThat(mergedPropertySources.getLocations()).isEqualTo(expectedLocations); Stream<String> locations = mergedPropertySources.getPropertySourceDescriptors().stream()
.map(PropertySourceDescriptor::locations).flatMap(List::stream);
softly.assertThat(locations).containsExactly(expectedLocations);
softly.assertThat(mergedPropertySources.getProperties()).isEqualTo(expectedProperties); softly.assertThat(mergedPropertySources.getProperties()).isEqualTo(expectedProperties);
}); });
} }

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2022 the original author or authors. * Copyright 2002-2023 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -33,14 +33,15 @@ class AnnotationConfigWebContextLoaderTests {
@Test @Test
void configMustNotContainLocations() throws Exception { @SuppressWarnings("deprecation")
void configMustNotContainLocations() {
AnnotationConfigWebContextLoader loader = new AnnotationConfigWebContextLoader(); AnnotationConfigWebContextLoader loader = new AnnotationConfigWebContextLoader();
WebMergedContextConfiguration mergedConfig = new WebMergedContextConfiguration(getClass(), WebMergedContextConfiguration mergedConfig = new WebMergedContextConfiguration(getClass(),
new String[] { "config.xml" }, EMPTY_CLASS_ARRAY, null, EMPTY_STRING_ARRAY, EMPTY_STRING_ARRAY, new String[] { "config.xml" }, EMPTY_CLASS_ARRAY, null, EMPTY_STRING_ARRAY, EMPTY_STRING_ARRAY,
EMPTY_STRING_ARRAY, "resource/path", loader, null, null); EMPTY_STRING_ARRAY, "resource/path", loader, null, null);
assertThatIllegalStateException() assertThatIllegalStateException()
.isThrownBy(() -> loader.loadContext(mergedConfig)) .isThrownBy(() -> loader.loadContext(mergedConfig))
.withMessageContaining("does not support resource locations"); .withMessageContaining("does not support resource locations");
} }
} }

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2022 the original author or authors. * Copyright 2002-2023 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -34,12 +34,13 @@ class GenericXmlWebContextLoaderTests {
@Test @Test
void configMustNotContainAnnotatedClasses() throws Exception { void configMustNotContainAnnotatedClasses() throws Exception {
GenericXmlWebContextLoader loader = new GenericXmlWebContextLoader(); GenericXmlWebContextLoader loader = new GenericXmlWebContextLoader();
@SuppressWarnings("deprecation")
WebMergedContextConfiguration mergedConfig = new WebMergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY, WebMergedContextConfiguration mergedConfig = new WebMergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY,
new Class<?>[] { getClass() }, null, EMPTY_STRING_ARRAY, EMPTY_STRING_ARRAY, EMPTY_STRING_ARRAY, new Class<?>[] { getClass() }, null, EMPTY_STRING_ARRAY, EMPTY_STRING_ARRAY, EMPTY_STRING_ARRAY,
"resource/path", loader, null, null); "resource/path", loader, null, null);
assertThatIllegalStateException() assertThatIllegalStateException()
.isThrownBy(() -> loader.loadContext(mergedConfig)) .isThrownBy(() -> loader.loadContext(mergedConfig))
.withMessageContaining("does not support annotated classes"); .withMessageContaining("does not support annotated classes");
} }
} }

View File

@ -0,0 +1,5 @@
key1: local file
environments:
dev:
url: https://dev.example.com
name: Developer Setup

View File

@ -0,0 +1,7 @@
environments:
dev:
url: https://dev.example.com
name: Developer Setup
prod:
url: https://prod.example.com
name: My Cool App