diff --git a/spring-test/src/main/java/org/springframework/test/context/ContextConfiguration.java b/spring-test/src/main/java/org/springframework/test/context/ContextConfiguration.java index 0212338e156..7719589b8b6 100644 --- a/spring-test/src/main/java/org/springframework/test/context/ContextConfiguration.java +++ b/spring-test/src/main/java/org/springframework/test/context/ContextConfiguration.java @@ -53,22 +53,19 @@ import org.springframework.context.ConfigurableApplicationContext; * The term annotated class can refer to any of the following. * * * *

- * Consult the Javadoc for {@link org.springframework.context.annotation.Configuration - * @Configuration} and {@link org.springframework.context.annotation.Bean @Bean} for - * further information regarding the configuration and semantics of - * annotated classes. + * Consult the Javadoc for {@link org.springframework.context.annotation.Configuration @Configuration} + * and {@link org.springframework.context.annotation.Bean @Bean} for further + * information regarding the configuration and semantics of annotated classes. * *

* As of Spring Framework 4.0, this annotation may be used as a meta-annotation @@ -78,6 +75,7 @@ import org.springframework.context.ConfigurableApplicationContext; * @since 2.5 * @see ContextHierarchy * @see ActiveProfiles + * @see TestPropertySource * @see ContextLoader * @see SmartContextLoader * @see ContextConfigurationAttributes diff --git a/spring-test/src/main/java/org/springframework/test/context/MergedContextConfiguration.java b/spring-test/src/main/java/org/springframework/test/context/MergedContextConfiguration.java index 5dc0f20cff4..a6f6ac01b30 100644 --- a/spring-test/src/main/java/org/springframework/test/context/MergedContextConfiguration.java +++ b/spring-test/src/main/java/org/springframework/test/context/MergedContextConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2014 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. @@ -34,15 +34,17 @@ import org.springframework.util.StringUtils; /** * {@code MergedContextConfiguration} encapsulates the merged * context configuration declared on a test class and all of its superclasses - * via {@link ContextConfiguration @ContextConfiguration} and - * {@link ActiveProfiles @ActiveProfiles}. + * via {@link ContextConfiguration @ContextConfiguration}, + * {@link ActiveProfiles @ActiveProfiles}, and + * {@link TestPropertySource @TestPropertySource}. * - *

Merged resource locations, annotated classes, and active profiles - * represent all declared values in the test class hierarchy taking into - * consideration the semantics of the - * {@link ContextConfiguration#inheritLocations inheritLocations} and - * {@link ActiveProfiles#inheritProfiles inheritProfiles} flags in - * {@code @ContextConfiguration} and {@code @ActiveProfiles}, respectively. + *

Merged context resource locations, annotated classes, active profiles, + * property resource locations, and in-lined properties represent all declared + * values in the test class hierarchy taking into consideration the semantics + * of the {@link ContextConfiguration#inheritLocations}, + * {@link ActiveProfiles#inheritProfiles}, + * {@link TestPropertySource#inheritLocations}, and + * {@link TestPropertySource#inheritProperties} flags. * *

A {@link SmartContextLoader} uses {@code MergedContextConfiguration} * to load an {@link org.springframework.context.ApplicationContext ApplicationContext}. @@ -73,13 +75,15 @@ public class MergedContextConfiguration implements Serializable { private final Class[] classes; private final Set>> contextInitializerClasses; private final String[] activeProfiles; + private final String[] propertySourceLocations; + private final String[] propertySourceProperties; private final ContextLoader contextLoader; private final CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate; private final MergedContextConfiguration parent; - private static String[] processLocations(String[] locations) { - return locations == null ? EMPTY_STRING_ARRAY : locations; + private static String[] processStrings(String[] array) { + return array == null ? EMPTY_STRING_ARRAY : array; } private static Class[] processClasses(Class[] classes) { @@ -115,20 +119,15 @@ public class MergedContextConfiguration implements Serializable { /** * Create a new {@code MergedContextConfiguration} instance for the - * supplied test class, resource locations, annotated classes, active - * profiles, and {@code ContextLoader}. - * - *

If a {@code null} value is supplied for {@code locations}, - * {@code classes}, or {@code activeProfiles} an empty array will - * be stored instead. Furthermore, active profiles will be sorted, and duplicate - * profiles will be removed. + * supplied parameters. + *

Delegates to + * {@link #MergedContextConfiguration(Class, String[], Class[], Set, String[], String[], String[], ContextLoader, CacheAwareContextLoaderDelegate, MergedContextConfiguration)}. * * @param testClass the test class for which the configuration was merged - * @param locations the merged resource locations + * @param locations the merged context resource locations * @param classes the merged annotated classes * @param activeProfiles the merged active bean definition profiles * @param contextLoader the resolved {@code ContextLoader} - * @see #MergedContextConfiguration(Class, String[], Class[], Set, String[], ContextLoader) */ public MergedContextConfiguration(Class testClass, String[] locations, Class[] classes, String[] activeProfiles, ContextLoader contextLoader) { @@ -137,18 +136,12 @@ public class MergedContextConfiguration implements Serializable { /** * Create a new {@code MergedContextConfiguration} instance for the - * supplied test class, resource locations, annotated classes, context - * initializers, active profiles, and {@code ContextLoader}. - * - *

If a {@code null} value is supplied for {@code locations}, - * {@code classes}, or {@code activeProfiles} an empty array will - * be stored instead. If a {@code null} value is supplied for the - * {@code contextInitializerClasses} an empty set will be stored instead. - * Furthermore, active profiles will be sorted, and duplicate profiles will - * be removed. + * supplied parameters. + *

Delegates to + * {@link #MergedContextConfiguration(Class, String[], Class[], Set, String[], String[], String[], ContextLoader, CacheAwareContextLoaderDelegate, MergedContextConfiguration)}. * * @param testClass the test class for which the configuration was merged - * @param locations the merged resource locations + * @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 @@ -166,19 +159,12 @@ public class MergedContextConfiguration implements Serializable { /** * Create a new {@code MergedContextConfiguration} instance for the - * supplied test class, resource locations, annotated classes, context - * initializers, active profiles, {@code ContextLoader}, and parent - * configuration. - * - *

If a {@code null} value is supplied for {@code locations}, - * {@code classes}, or {@code activeProfiles} an empty array will - * be stored instead. If a {@code null} value is supplied for the - * {@code contextInitializerClasses} an empty set will be stored instead. - * Furthermore, active profiles will be sorted, and duplicate profiles will - * be removed. + * supplied parameters. + *

Delegates to + * {@link #MergedContextConfiguration(Class, String[], Class[], Set, String[], String[], String[], ContextLoader, CacheAwareContextLoaderDelegate, MergedContextConfiguration)}. * * @param testClass the test class for which the configuration was merged - * @param locations the merged resource locations + * @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 @@ -195,11 +181,50 @@ public class MergedContextConfiguration implements Serializable { Set>> contextInitializerClasses, String[] activeProfiles, ContextLoader contextLoader, CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate, MergedContextConfiguration parent) { + this(testClass, locations, classes, contextInitializerClasses, activeProfiles, null, null, contextLoader, + cacheAwareContextLoaderDelegate, parent); + } + + /** + * Create a new {@code MergedContextConfiguration} instance for the + * supplied parameters. + * + *

If a {@code null} value is supplied for {@code locations}, + * {@code classes}, {@code activeProfiles}, {@code propertySourceLocations}, + * or {@code propertySourceProperties} an empty array will be stored instead. + * If a {@code null} value is supplied for the + * {@code contextInitializerClasses} 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 propertySourceLocations the merged {@code PropertySource} locations + * @param propertySourceProperties the merged {@code PropertySource} properties + * @param contextLoader the resolved {@code ContextLoader} + * @param cacheAwareContextLoaderDelegate a cache-aware context loader + * delegate with which to retrieve the parent context + * @param parent the parent configuration or {@code null} if there is no parent + * @since 4.1 + */ + public MergedContextConfiguration( + Class testClass, + String[] locations, + Class[] classes, + Set>> contextInitializerClasses, + String[] activeProfiles, String[] propertySourceLocations, String[] propertySourceProperties, + ContextLoader contextLoader, CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate, + MergedContextConfiguration parent) { this.testClass = testClass; - this.locations = processLocations(locations); + this.locations = processStrings(locations); this.classes = processClasses(classes); this.contextInitializerClasses = processContextInitializerClasses(contextInitializerClasses); this.activeProfiles = processActiveProfiles(activeProfiles); + this.propertySourceLocations = processStrings(propertySourceLocations); + this.propertySourceProperties = processStrings(propertySourceProperties); this.contextLoader = contextLoader; this.cacheAwareContextLoaderDelegate = cacheAwareContextLoaderDelegate; this.parent = parent; @@ -213,7 +238,10 @@ public class MergedContextConfiguration implements Serializable { } /** - * Get the merged resource locations for the {@linkplain #getTestClass() test class}. + * Get the merged resource locations for {@code ApplicationContext} + * configuration files for the {@linkplain #getTestClass() test class}. + *

Context resource locations typically represent XML configuration + * files or Groovy scripts. */ public String[] getLocations() { return locations; @@ -228,7 +256,7 @@ public class MergedContextConfiguration implements Serializable { /** * Determine if this {@code MergedContextConfiguration} instance has - * path-based resource locations. + * path-based context resource locations. * * @return {@code true} if the {@link #getLocations() locations} array is not empty * @since 4.0.4 @@ -254,7 +282,7 @@ public class MergedContextConfiguration implements Serializable { /** * Determine if this {@code MergedContextConfiguration} instance has - * either path-based resource locations or class-based resources. + * either path-based context resource locations or class-based resources. * * @return {@code true} if either the {@link #getLocations() locations} * or the {@link #getClasses() classes} array is not empty @@ -275,12 +303,36 @@ public class MergedContextConfiguration implements Serializable { } /** - * Get the merged active bean definition profiles for the {@linkplain #getTestClass() test class}. + * Get the merged active bean definition profiles for the + * {@linkplain #getTestClass() test class}. + * @see ActiveProfiles */ public String[] getActiveProfiles() { return activeProfiles; } + /** + * Get the merged resource locations for test {@code PropertySources} for the + * {@linkplain #getTestClass() test class}. + * @see TestPropertySource#locations + * @see java.util.Properties + */ + public String[] getPropertySourceLocations() { + return propertySourceLocations; + } + + /** + * Get the merged test {@code PropertySource} properties for the + * {@linkplain #getTestClass() test class}. + *

Properties will be loaded into the {@code Environment}'s set of + * {@code PropertySources}. + * @see TestPropertySource#properties + * @see java.util.Properties + */ + public String[] getPropertySourceProperties() { + return propertySourceProperties; + } + /** * Get the resolved {@link ContextLoader} for the {@linkplain #getTestClass() test class}. */ @@ -334,6 +386,8 @@ public class MergedContextConfiguration implements Serializable { result = prime * result + Arrays.hashCode(classes); result = prime * result + contextInitializerClasses.hashCode(); result = prime * result + Arrays.hashCode(activeProfiles); + result = prime * result + Arrays.hashCode(propertySourceLocations); + result = prime * result + Arrays.hashCode(propertySourceProperties); result = prime * result + (parent == null ? 0 : parent.hashCode()); result = prime * result + nullSafeToString(contextLoader).hashCode(); return result; @@ -345,6 +399,8 @@ public class MergedContextConfiguration implements Serializable { * {@linkplain #getClasses() annotated classes}, * {@linkplain #getContextInitializerClasses() context initializer classes}, * {@linkplain #getActiveProfiles() active profiles}, + * {@linkplain #getPropertySourceLocations() property source locations}, + * {@linkplain #getPropertySourceProperties() property source properties}, * {@linkplain #getParent() parents}, and the fully qualified names of their * {@link #getContextLoader() ContextLoaders}. */ @@ -376,6 +432,14 @@ public class MergedContextConfiguration implements Serializable { return false; } + if (!Arrays.equals(this.propertySourceLocations, that.propertySourceLocations)) { + return false; + } + + if (!Arrays.equals(this.propertySourceProperties, that.propertySourceProperties)) { + return false; + } + if (this.parent == null) { if (that.parent != null) { return false; @@ -396,8 +460,10 @@ public class MergedContextConfiguration implements Serializable { * Provide a String representation of the {@linkplain #getTestClass() test class}, * {@linkplain #getLocations() locations}, {@linkplain #getClasses() annotated classes}, * {@linkplain #getContextInitializerClasses() context initializer classes}, - * {@linkplain #getActiveProfiles() active profiles}, the name of the - * {@link #getContextLoader() ContextLoader}, and the + * {@linkplain #getActiveProfiles() active profiles}, + * {@linkplain #getPropertySourceLocations() property source locations}, + * {@linkplain #getPropertySourceProperties() property source properties}, + * the name of the {@link #getContextLoader() ContextLoader}, and the * {@linkplain #getParent() parent configuration}. */ @Override @@ -408,6 +474,8 @@ public class MergedContextConfiguration implements Serializable { .append("classes", ObjectUtils.nullSafeToString(classes))// .append("contextInitializerClasses", ObjectUtils.nullSafeToString(contextInitializerClasses))// .append("activeProfiles", ObjectUtils.nullSafeToString(activeProfiles))// + .append("propertySourceLocations", ObjectUtils.nullSafeToString(propertySourceLocations))// + .append("propertySourceProperties", ObjectUtils.nullSafeToString(propertySourceProperties))// .append("contextLoader", nullSafeToString(contextLoader))// .append("parent", parent)// .toString(); diff --git a/spring-test/src/main/java/org/springframework/test/context/TestPropertySource.java b/spring-test/src/main/java/org/springframework/test/context/TestPropertySource.java new file mode 100644 index 00000000000..281994544ec --- /dev/null +++ b/spring-test/src/main/java/org/springframework/test/context/TestPropertySource.java @@ -0,0 +1,250 @@ +/* + * Copyright 2002-2014 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 + * + * http://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; + +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 @TestPropertySource} is a class-level annotation that is used to + * configure the {@link #locations} of properties files and inlined + * {@link #properties} to be added to the {@code Environment}'s set of + * {@code PropertySources} for an + * {@link org.springframework.context.ApplicationContext ApplicationContext} + * for integration tests. + * + *

Precedence

+ *

Test property sources have higher precedence than those loaded from the + * operating system's environment or Java system properties as well as property + * sources added by the application declaratively via + * {@link org.springframework.context.annotation.PropertySource @PropertySource} + * or programmatically (e.g., via an + * {@link org.springframework.context.ApplicationContextInitializer ApplicationContextInitializer} + * or some other means). Thus, test property sources can be used to selectively + * override properties defined in system and application property sources. + * Furthermore, inlined {@link #properties} have higher precedence than + * properties loaded from resource {@link #locations}. + * + *

Default Properties File Detection

+ *

If {@code @TestPropertySource} is declared as an empty annotation + * (i.e., without explicit values for {@link #locations} or {@link #properties}), + * an attempt will be made to detect a default properties file relative + * to the class that declared the annotation. For example, if the annotated test + * class is {@code com.example.MyTest}, the corresponding default properties file + * is {@code "classpath:com/example/MyTest.properties"}. If the default cannot be + * detected, an {@link IllegalStateException} will be thrown. + * + *

Enabling @TestPropertySource

+ *

{@code @TestPropertySource} is enabled if the configured + * {@linkplain ContextConfiguration#loader context loader} honors it. Every + * {@code SmartContextLoader} that is a subclass of either + * {@link org.springframework.test.context.support.AbstractGenericContextLoader AbstractGenericContextLoader} or + * {@link org.springframework.test.context.web.AbstractGenericWebContextLoader AbstractGenericWebContextLoader} + * provides automatic support for {@code @TestPropertySource}, and this includes + * every {@code SmartContextLoader} provided by the Spring TestContext Framework. + * + *

Miscellaneous

+ * + * + * @author Sam Brannen + * @since 4.1 + * @see ContextConfiguration + * @see org.springframework.core.env.Environment + * @see org.springframework.core.env.PropertySource + * @see org.springframework.context.annotation.PropertySource + */ +@Documented +@Inherited +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface TestPropertySource { + + /** + * Alias for {@link #locations}. + * + *

This attribute may not be used in conjunction with + * {@link #locations}, but it may be used instead of {@link #locations}. + * + * @see #locations + */ + String[] value() default {}; + + /** + * The resource locations of properties files to be loaded into the + * {@code Environment}'s set of {@code PropertySources}. Each location + * will be added to the enclosing {@code Environment} as its own property + * source, in the order declared. + * + *

Supported File Formats

+ *

Both traditional and XML-based properties file formats are supported + * — for example, {@code "classpath:/com/example/test.properties"} + * or {@code "file:/path/to/file.xml"}. + * + *

Path Resource Semantics

+ *

Each path will be interpreted as a Spring + * {@link org.springframework.core.io.Resource Resource}. A plain path + * — for example, {@code "test.properties"} — will be treated as a + * classpath resource that is relative to the package in which the + * test class is defined. A path starting with a slash will be treated as an + * absolute classpath resource, for example: + * {@code "/org/example/test.xml"}. A path which references a + * URL (e.g., a path prefixed with + * {@link org.springframework.util.ResourceUtils#CLASSPATH_URL_PREFIX classpath:}, + * {@link org.springframework.util.ResourceUtils#FILE_URL_PREFIX file:}, + * {@code http:}, etc.) will be loaded using the specified resource protocol. + * Resource location wildcards (e.g. **/*.properties) + * are not permitted: each location must evaluate to exactly one + * {@code .properties} or {@code .xml} resource. + * + *

Default Properties File Detection

+ *

See the class-level Javadoc for a discussion on detection of defaults. + * + *

Precedence

+ *

Properties loaded from resource locations have lower precedence than + * inlined {@link #properties}. + * + *

This attribute may not be used in conjunction with + * {@link #value}, but it may be used instead of {@link #value}. + * + * @see #inheritLocations + * @see #value + * @see #properties + * @see org.springframework.core.env.PropertySource + */ + String[] locations() default {}; + + /** + * Whether or not test property source {@link #locations} from superclasses + * should be inherited. + * + *

The default value is {@code true}, which means that a test class will + * inherit property source locations defined by a superclass. + * Specifically, the property source locations for a test class will be + * appended to the list of property source locations defined by a superclass. + * Thus, subclasses have the option of extending the list of test + * property source locations. + * + *

If {@code inheritLocations} is set to {@code false}, the property + * source locations for the test class will shadow and effectively + * replace any property source locations defined by a superclass. + * + *

In the following example, the {@code ApplicationContext} for + * {@code BaseTest} will be loaded using only the {@code "base.properties"} + * file as a test property source. In contrast, the {@code ApplicationContext} + * for {@code ExtendedTest} will be loaded using the {@code "base.properties"} + * and {@code "extended.properties"} files as test property + * source locations. + *

+	 * @TestPropertySource("base.properties")
+	 * @ContextConfiguration
+	 * public class BaseTest {
+	 *     // ...
+	 * }
+	 *
+	 * @TestPropertySource("extended.properties")
+	 * @ContextConfiguration
+	 * public class ExtendedTest extends BaseTest {
+	 *     // ...
+	 * }
+	 * 
+ * + * @see #locations + */ + boolean inheritLocations() default true; + + /** + * Inlined properties in the form of key-value pairs that + * should be added to the Spring + * {@link org.springframework.core.env.Environment Environment} before the + * {@code ApplicationContext} is loaded for the test. All key-value pairs + * will be added to the enclosing {@code Environment} as a single test + * {@code PropertySource} with the highest precedence. + * + *

Supported Syntax

+ *

The supported syntax for key-value pairs is the same as the + * syntax defined for entries in a Java + * {@linkplain java.util.Properties#load(java.io.Reader) properties file}: + *

+ * + *

Precedence

+ *

Properties declared via this attribute have higher precedence than + * properties loaded from resource {@link locations}. + * + *

This attribute may be used in conjunction with {@link #value} + * or {@link #locations}. + * + * @see #inheritProperties + * @see #locations + * @see org.springframework.core.env.PropertySource + */ + String[] properties() default {}; + + /** + * Whether or not inlined test {@link #properties} from superclasses should + * be inherited. + * + *

The default value is {@code true}, which means that a test class will + * inherit inlined properties defined by a superclass. Specifically, + * the inlined properties for a test class will be appended to the list of + * inlined properties defined by a superclass. Thus, subclasses have the + * option of extending the list of inlined test properties. + * + *

If {@code inheritProperties} is set to {@code false}, the inlined + * properties for the test class will shadow and effectively + * replace any inlined properties defined by a superclass. + * + *

In the following example, the {@code ApplicationContext} for + * {@code BaseTest} will be loaded using only the inlined {@code key1} + * property. In contrast, the {@code ApplicationContext} for + * {@code ExtendedTest} will be loaded using the inlined {@code key1} + * and {@code key2} properties. + *

+	 * @TestPropertySource(properties = "key1 = value1")
+	 * @ContextConfiguration
+	 * public class BaseTest {
+	 *     // ...
+	 * }
+	 *
+	 * @TestPropertySource(properties = "key2 = value2")
+	 * @ContextConfiguration
+	 * public class ExtendedTest extends BaseTest {
+	 *     // ...
+	 * }
+	 * 
+ * + * @see #properties + */ + boolean inheritProperties() default true; + +} diff --git a/spring-test/src/main/java/org/springframework/test/context/support/AbstractContextLoader.java b/spring-test/src/main/java/org/springframework/test/context/support/AbstractContextLoader.java index 23181b8a886..9bf45de085a 100644 --- a/spring-test/src/main/java/org/springframework/test/context/support/AbstractContextLoader.java +++ b/spring-test/src/main/java/org/springframework/test/context/support/AbstractContextLoader.java @@ -16,8 +16,13 @@ package org.springframework.test.context.support; +import java.io.IOException; +import java.io.StringReader; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import java.util.Properties; import java.util.Set; import org.apache.commons.logging.Log; @@ -28,7 +33,12 @@ import org.springframework.context.ApplicationContextInitializer; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.core.GenericTypeResolver; import org.springframework.core.annotation.AnnotationAwareOrderComparator; +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.core.env.MapPropertySource; +import org.springframework.core.env.PropertySource; import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.Resource; +import org.springframework.core.io.support.ResourcePropertySource; import org.springframework.test.context.ContextConfigurationAttributes; import org.springframework.test.context.ContextLoader; import org.springframework.test.context.MergedContextConfiguration; @@ -53,6 +63,7 @@ import org.springframework.util.ResourceUtils; * * @author Sam Brannen * @author Juergen Hoeller + * @author Dave Syer * @since 2.5 * @see #generateDefaultLocations * @see #getResourceSuffixes @@ -62,6 +73,8 @@ public abstract class AbstractContextLoader implements SmartContextLoader { private static final String[] EMPTY_STRING_ARRAY = new String[0]; + private static final String LINE_SEPARATOR = System.getProperty("line.separator"); + private static final Log logger = LogFactory.getLog(AbstractContextLoader.class); @@ -96,15 +109,24 @@ public abstract class AbstractContextLoader implements SmartContextLoader { * - *

Any {@code ApplicationContextInitializers} implementing - * {@link org.springframework.core.Ordered Ordered} or marked with {@link - * org.springframework.core.annotation.Order @Order} will be sorted appropriately. * @param context the newly created application context * @param mergedConfig the merged context configuration * @since 3.2 @@ -112,10 +134,79 @@ public abstract class AbstractContextLoader implements SmartContextLoader { * @see #loadContext(MergedContextConfiguration) * @see ConfigurableApplicationContext#setId */ - @SuppressWarnings("unchecked") protected void prepareContext(ConfigurableApplicationContext context, MergedContextConfiguration mergedConfig) { context.getEnvironment().setActiveProfiles(mergedConfig.getActiveProfiles()); + addResourcePropertySourcesToEnvironment(context, mergedConfig); + addInlinedPropertiesToEnvironment(context, mergedConfig); + invokeApplicationContextInitializers(context, mergedConfig); + } + + /** + * @since 4.1 + */ + private void addResourcePropertySourcesToEnvironment(ConfigurableApplicationContext context, + MergedContextConfiguration mergedConfig) { + try { + ConfigurableEnvironment environment = context.getEnvironment(); + String[] locations = mergedConfig.getPropertySourceLocations(); + for (String location : locations) { + String resolvedLocation = environment.resolveRequiredPlaceholders(location); + Resource resource = context.getResource(resolvedLocation); + ResourcePropertySource ps = new ResourcePropertySource(resource); + environment.getPropertySources().addFirst(ps); + } + } + catch (IOException e) { + throw new IllegalStateException("Failed to add PropertySource to Environment", e); + } + } + + /** + * @since 4.1 + */ + private void addInlinedPropertiesToEnvironment(ConfigurableApplicationContext context, + MergedContextConfiguration mergedConfig) { + String[] keyValuePairs = mergedConfig.getPropertySourceProperties(); + if (!ObjectUtils.isEmpty(keyValuePairs)) { + String name = "test properties " + ObjectUtils.nullSafeToString(keyValuePairs); + MapPropertySource ps = new MapPropertySource(name, extractEnvironmentProperties(keyValuePairs)); + context.getEnvironment().getPropertySources().addFirst(ps); + } + } + /** + * Extract environment properties from the supplied key/value pairs. + *

Parsing of the key/value pairs is achieved by converting all pairs + * into a single virtual properties file in memory and delegating + * to {@link Properties#load(java.io.Reader)} to parse that virtual file. + *

This code has been adapted from Spring Boot's + * {@link org.springframework.boot.test.SpringApplicationContextLoader SpringApplicationContextLoader}. + * @since 4.1 + */ + private Map extractEnvironmentProperties(String[] keyValuePairs) { + StringBuilder sb = new StringBuilder(); + for (String keyValuePair : keyValuePairs) { + sb.append(keyValuePair).append(LINE_SEPARATOR); + } + String content = sb.toString(); + Properties props = new Properties(); + try { + props.load(new StringReader(content)); + } + catch (IOException e) { + throw new IllegalStateException("Failed to load test environment properties from: " + content, e); + } + + Map properties = new HashMap(); + for (String name : props.stringPropertyNames()) { + properties.put(name, props.getProperty(name)); + } + return properties; + } + + @SuppressWarnings("unchecked") + private void invokeApplicationContextInitializers(ConfigurableApplicationContext context, + MergedContextConfiguration mergedConfig) { Set>> initializerClasses = mergedConfig.getContextInitializerClasses(); if (initializerClasses.isEmpty()) { // no ApplicationContextInitializers have been declared -> nothing to do diff --git a/spring-test/src/main/java/org/springframework/test/context/support/AbstractTestContextBootstrapper.java b/spring-test/src/main/java/org/springframework/test/context/support/AbstractTestContextBootstrapper.java index cfdca3b7af4..cf2b9f7a146 100644 --- a/spring-test/src/main/java/org/springframework/test/context/support/AbstractTestContextBootstrapper.java +++ b/spring-test/src/main/java/org/springframework/test/context/support/AbstractTestContextBootstrapper.java @@ -26,7 +26,6 @@ import java.util.Set; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; - import org.springframework.beans.BeanInstantiationException; import org.springframework.beans.BeanUtils; import org.springframework.context.ApplicationContextInitializer; @@ -100,13 +99,14 @@ public abstract class AbstractTestContextBootstrapper implements TestContextBoot Class annotationType = TestExecutionListeners.class; List> classesList = new ArrayList>(); - AnnotationDescriptor descriptor = - MetaAnnotationUtils.findAnnotationDescriptor(clazz, annotationType); + AnnotationDescriptor descriptor = MetaAnnotationUtils.findAnnotationDescriptor(clazz, + annotationType); // Use defaults? if (descriptor == null) { if (logger.isDebugEnabled()) { - logger.debug("@TestExecutionListeners is not present for class [" + clazz.getName() + "]: using defaults."); + logger.debug("@TestExecutionListeners is not present for class [" + clazz.getName() + + "]: using defaults."); } classesList.addAll(getDefaultTestExecutionListenerClasses()); } @@ -116,19 +116,19 @@ public abstract class AbstractTestContextBootstrapper implements TestContextBoot Class declaringClass = descriptor.getDeclaringClass(); AnnotationAttributes annAttrs = descriptor.getAnnotationAttributes(); if (logger.isTraceEnabled()) { - logger.trace(String.format("Retrieved @TestExecutionListeners attributes [%s] for declaring class [%s].", - annAttrs, declaringClass.getName())); + logger.trace(String.format( + "Retrieved @TestExecutionListeners attributes [%s] for declaring class [%s].", annAttrs, + declaringClass.getName())); } - Class[] valueListenerClasses = - (Class[]) annAttrs.getClassArray("value"); - Class[] listenerClasses = - (Class[]) annAttrs.getClassArray("listeners"); + Class[] valueListenerClasses = (Class[]) annAttrs.getClassArray("value"); + Class[] listenerClasses = (Class[]) annAttrs.getClassArray("listeners"); if (!ObjectUtils.isEmpty(valueListenerClasses) && !ObjectUtils.isEmpty(listenerClasses)) { - throw new IllegalStateException(String.format("Class [%s] configured with @TestExecutionListeners' " + - "'value' [%s] and 'listeners' [%s] attributes. Use one or the other, but not both.", - declaringClass.getName(), ObjectUtils.nullSafeToString(valueListenerClasses), - ObjectUtils.nullSafeToString(listenerClasses))); + throw new IllegalStateException(String.format( + "Class [%s] configured with @TestExecutionListeners' " + + "'value' [%s] and 'listeners' [%s] attributes. Use one or the other, but not both.", + declaringClass.getName(), ObjectUtils.nullSafeToString(valueListenerClasses), + ObjectUtils.nullSafeToString(listenerClasses))); } else if (!ObjectUtils.isEmpty(valueListenerClasses)) { listenerClasses = valueListenerClasses; @@ -138,7 +138,7 @@ public abstract class AbstractTestContextBootstrapper implements TestContextBoot classesList.addAll(0, Arrays.> asList(listenerClasses)); } descriptor = (annAttrs.getBoolean("inheritListeners") ? MetaAnnotationUtils.findAnnotationDescriptor( - descriptor.getRootDeclaringClass().getSuperclass(), annotationType) : null); + descriptor.getRootDeclaringClass().getSuperclass(), annotationType) : null); } } @@ -158,10 +158,10 @@ public abstract class AbstractTestContextBootstrapper implements TestContextBoot } if (ncdfe != null) { if (logger.isInfoEnabled()) { - logger.info(String.format("Could not instantiate TestExecutionListener [%s]. " + - "Specify custom listener classes or make the default listener classes " + - "(and their required dependencies) available. Offending class: [%s]", - listenerClass.getName(), ncdfe.getMessage())); + logger.info(String.format("Could not instantiate TestExecutionListener [%s]. " + + "Specify custom listener classes or make the default listener classes " + + "(and their required dependencies) available. Offending class: [%s]", + listenerClass.getName(), ncdfe.getMessage())); } } } @@ -186,8 +186,8 @@ public abstract class AbstractTestContextBootstrapper implements TestContextBoot } catch (Throwable ex) { if (logger.isDebugEnabled()) { - logger.debug("Could not load default TestExecutionListener class [" + className + - "]. Specify custom listener classes or make the default listener classes available.", ex); + logger.debug("Could not load default TestExecutionListener class [" + className + + "]. Specify custom listener classes or make the default listener classes available.", ex); } } } @@ -206,15 +206,15 @@ public abstract class AbstractTestContextBootstrapper implements TestContextBoot if (MetaAnnotationUtils.findAnnotationDescriptorForTypes(testClass, ContextConfiguration.class, ContextHierarchy.class) == null) { if (logger.isInfoEnabled()) { - logger.info(String.format("Neither @ContextConfiguration nor @ContextHierarchy found for test class [%s]", - testClass.getName())); + logger.info(String.format( + "Neither @ContextConfiguration nor @ContextHierarchy found for test class [%s]", + testClass.getName())); } return new MergedContextConfiguration(testClass, null, null, null, null); } if (AnnotationUtils.findAnnotation(testClass, ContextHierarchy.class) != null) { - Map> hierarchyMap = - ContextLoaderUtils.buildContextHierarchyMap(testClass); + Map> hierarchyMap = ContextLoaderUtils.buildContextHierarchyMap(testClass); MergedContextConfiguration parentConfig = null; MergedContextConfiguration mergedConfig = null; @@ -228,8 +228,8 @@ public abstract class AbstractTestContextBootstrapper implements TestContextBoot Assert.notEmpty(reversedList, "ContextConfigurationAttributes list must not be empty"); Class declaringClass = reversedList.get(0).getDeclaringClass(); - mergedConfig = buildMergedContextConfiguration( - declaringClass, reversedList, parentConfig, cacheAwareContextLoaderDelegate); + mergedConfig = buildMergedContextConfiguration(declaringClass, reversedList, parentConfig, + cacheAwareContextLoaderDelegate); parentConfig = mergedConfig; } @@ -238,8 +238,8 @@ public abstract class AbstractTestContextBootstrapper implements TestContextBoot } else { return buildMergedContextConfiguration(testClass, - ContextLoaderUtils.resolveContextConfigurationAttributes(testClass), null, - cacheAwareContextLoaderDelegate); + ContextLoaderUtils.resolveContextConfigurationAttributes(testClass), null, + cacheAwareContextLoaderDelegate); } } @@ -276,7 +276,7 @@ public abstract class AbstractTestContextBootstrapper implements TestContextBoot for (ContextConfigurationAttributes configAttributes : configAttributesList) { if (logger.isTraceEnabled()) { logger.trace(String.format("Processing locations and classes for context configuration attributes %s", - configAttributes)); + configAttributes)); } if (contextLoader instanceof SmartContextLoader) { SmartContextLoader smartContextLoader = (SmartContextLoader) contextLoader; @@ -286,7 +286,7 @@ public abstract class AbstractTestContextBootstrapper implements TestContextBoot } else { String[] processedLocations = contextLoader.processLocations(configAttributes.getDeclaringClass(), - configAttributes.getLocations()); + configAttributes.getLocations()); locationsList.addAll(0, Arrays.asList(processedLocations)); // Legacy ContextLoaders don't know how to process classes } @@ -300,9 +300,11 @@ public abstract class AbstractTestContextBootstrapper implements TestContextBoot Set>> initializerClasses = // ApplicationContextInitializerUtils.resolveInitializerClasses(configAttributesList); String[] activeProfiles = ActiveProfilesUtils.resolveActiveProfiles(testClass); + MergedTestPropertySources mergedTestPropertySources = TestPropertySourceUtils.buildMergedTestPropertySources(testClass); return buildMergedContextConfiguration(testClass, locations, classes, initializerClasses, activeProfiles, - contextLoader, cacheAwareContextLoaderDelegate, parentConfig); + mergedTestPropertySources.getLocations(), mergedTestPropertySources.getProperties(), contextLoader, + cacheAwareContextLoaderDelegate, parentConfig); } /** @@ -334,7 +336,7 @@ public abstract class AbstractTestContextBootstrapper implements TestContextBoot } if (logger.isTraceEnabled()) { logger.trace(String.format("Using ContextLoader class [%s] for test class [%s]", - contextLoaderClass.getName(), testClass.getName())); + contextLoaderClass.getName(), testClass.getName())); } return BeanUtils.instantiateClass(contextLoaderClass, ContextLoader.class); } @@ -413,6 +415,8 @@ public abstract class AbstractTestContextBootstrapper implements TestContextBoot * @param classes the merged annotated classes * @param initializerClasses the merged context initializer classes * @param activeProfiles the merged active bean definition profiles + * @param propertySourceLocations the merged {@code PropertySource} locations + * @param propertySourceProperties the merged {@code PropertySource} properties * @param contextLoader the resolved {@code ContextLoader} * @param cacheAwareContextLoaderDelegate the cache-aware context loader delegate * to be provided to the instantiated {@code MergedContextConfiguration} @@ -421,9 +425,12 @@ public abstract class AbstractTestContextBootstrapper implements TestContextBoot * @return the fully initialized {@code MergedContextConfiguration} */ protected abstract MergedContextConfiguration buildMergedContextConfiguration( - Class testClass, String[] locations, Class[] classes, + Class testClass, + String[] locations, + Class[] classes, Set>> initializerClasses, - String[] activeProfiles, ContextLoader contextLoader, - CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate, MergedContextConfiguration parentConfig); + String[] activeProfiles, String[] propertySourceLocations, String[] propertySourceProperties, + ContextLoader contextLoader, CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate, + MergedContextConfiguration parentConfig); } diff --git a/spring-test/src/main/java/org/springframework/test/context/support/DefaultTestContextBootstrapper.java b/spring-test/src/main/java/org/springframework/test/context/support/DefaultTestContextBootstrapper.java index 19d89b98717..4c3f8f9861b 100644 --- a/spring-test/src/main/java/org/springframework/test/context/support/DefaultTestContextBootstrapper.java +++ b/spring-test/src/main/java/org/springframework/test/context/support/DefaultTestContextBootstrapper.java @@ -81,16 +81,19 @@ public class DefaultTestContextBootstrapper extends AbstractTestContextBootstrap /** * Builds a standard {@link MergedContextConfiguration}. */ + @Override protected MergedContextConfiguration buildMergedContextConfiguration( Class testClass, String[] locations, Class[] classes, Set>> initializerClasses, - String[] activeProfiles, ContextLoader contextLoader, - CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate, MergedContextConfiguration parentConfig) { + String[] activeProfiles, String[] propertySourceLocations, String[] propertySourceProperties, + ContextLoader contextLoader, CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate, + MergedContextConfiguration parentConfig) { return new MergedContextConfiguration(testClass, locations, classes, initializerClasses, activeProfiles, - contextLoader, cacheAwareContextLoaderDelegate, parentConfig); + propertySourceLocations, propertySourceProperties, contextLoader, cacheAwareContextLoaderDelegate, + parentConfig); } } diff --git a/spring-test/src/main/java/org/springframework/test/context/support/MergedTestPropertySources.java b/spring-test/src/main/java/org/springframework/test/context/support/MergedTestPropertySources.java new file mode 100644 index 00000000000..a50933a719b --- /dev/null +++ b/spring-test/src/main/java/org/springframework/test/context/support/MergedTestPropertySources.java @@ -0,0 +1,78 @@ +/* + * Copyright 2002-2014 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 + * + * http://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.support; + +import org.springframework.test.context.TestPropertySource; +import org.springframework.util.Assert; + +/** + * {@code MergedTestPropertySources} encapsulates the merged + * property sources declared on a test class and all of its superclasses + * via {@link TestPropertySource @TestPropertySource}. + * + * @author Sam Brannen + * @since 4.1 + * @see TestPropertySource + */ +class MergedTestPropertySources { + + private static final String[] EMPTY_STRING_ARRAY = new String[0]; + + private final String[] locations; + + private final String[] properties; + + + /** + * Create an empty {@code MergedTestPropertySources} instance. + */ + MergedTestPropertySources() { + this(EMPTY_STRING_ARRAY, EMPTY_STRING_ARRAY); + } + + /** + * Create a {@code MergedTestPropertySources} instance with the supplied + * {@code locations} and {@code properties}. + * @param locations the resource locations of properties files; may be + * empty but never {@code null} + * @param properties the properties in the form of {@code key=value} pairs; + * may be empty but never {@code null} + */ + MergedTestPropertySources(String[] locations, String[] properties) { + Assert.notNull(locations, "The locations array must not be null"); + Assert.notNull(properties, "The properties array must not be null"); + this.locations = locations; + this.properties = properties; + } + + /** + * Get the resource locations of properties files. + * @see TestPropertySource#locations() + */ + String[] getLocations() { + return this.locations; + } + + /** + * Get the properties in the form of key-value pairs. + * @see TestPropertySource#properties() + */ + String[] getProperties() { + return this.properties; + } + +} diff --git a/spring-test/src/main/java/org/springframework/test/context/support/TestPropertySourceAttributes.java b/spring-test/src/main/java/org/springframework/test/context/support/TestPropertySourceAttributes.java new file mode 100644 index 00000000000..c253af169c2 --- /dev/null +++ b/spring-test/src/main/java/org/springframework/test/context/support/TestPropertySourceAttributes.java @@ -0,0 +1,209 @@ +/* + * Copyright 2002-2014 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 + * + * http://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.support; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.core.annotation.AnnotationAttributes; +import org.springframework.core.io.ClassPathResource; +import org.springframework.core.style.ToStringCreator; +import org.springframework.test.context.TestPropertySource; +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; +import org.springframework.util.ObjectUtils; +import org.springframework.util.ResourceUtils; + +/** + * {@code TestPropertySourceAttributes} encapsulates the attributes declared + * via {@link TestPropertySource @TestPropertySource}. + * + *

In addition to encapsulating declared attributes, + * {@code TestPropertySourceAttributes} also enforces configuration rules + * and detects default properties files. + * + * @author Sam Brannen + * @since 4.1 + * @see TestPropertySource + * @see MergedTestPropertySources + */ +class TestPropertySourceAttributes { + + private static final Log logger = LogFactory.getLog(TestPropertySourceAttributes.class); + + private final Class declaringClass; + + private final String[] locations; + + private final boolean inheritLocations; + + private final String[] properties; + + private final boolean inheritProperties; + + + /** + * Create a new {@code TestPropertySourceAttributes} instance for the + * supplied {@link AnnotationAttributes} (parsed from a + * {@link TestPropertySource @TestPropertySource} annotation) and + * the {@linkplain Class test class} that declared them, enforcing + * configuration rules and detecting a default properties file if + * necessary. + * @param declaringClass the class that declared {@code @TestPropertySource} + * @param annAttrs the annotation attributes from which to retrieve the attributes + */ + TestPropertySourceAttributes(Class declaringClass, AnnotationAttributes annAttrs) { + this(declaringClass, resolveLocations(declaringClass, annAttrs.getStringArray("locations"), + annAttrs.getStringArray("value")), annAttrs.getBoolean("inheritLocations"), + annAttrs.getStringArray("properties"), annAttrs.getBoolean("inheritProperties")); + } + + private TestPropertySourceAttributes(Class declaringClass, String[] locations, boolean inheritLocations, + String[] properties, boolean inheritProperties) { + Assert.notNull(declaringClass, "declaringClass must not be null"); + + if (ObjectUtils.isEmpty(locations) && ObjectUtils.isEmpty(properties)) { + locations = new String[] { detectDefaultPropertiesFile(declaringClass) }; + } + + this.declaringClass = declaringClass; + this.locations = locations; + this.inheritLocations = inheritLocations; + this.properties = properties; + this.inheritProperties = inheritProperties; + } + + /** + * Get the {@linkplain Class class} that declared {@code @TestPropertySource}. + * + * @return the declaring class; never {@code null} + */ + Class getDeclaringClass() { + return declaringClass; + } + + /** + * Get the resource locations that were declared via {@code @TestPropertySource}. + * + *

Note: The returned value may represent a detected default + * that does not match the original value declared via {@code @TestPropertySource}. + * + * @return the resource locations; potentially {@code null} or empty + * @see TestPropertySource#value + * @see TestPropertySource#locations + * @see #setLocations(String[]) + */ + String[] getLocations() { + return locations; + } + + /** + * Get the {@code inheritLocations} flag that was declared via {@code @TestPropertySource}. + * + * @return the {@code inheritLocations} flag + * @see TestPropertySource#inheritLocations + */ + boolean isInheritLocations() { + return inheritLocations; + } + + /** + * Get the inlined properties that were declared via {@code @TestPropertySource}. + * + * @return the inlined properties; potentially {@code null} or empty + * @see TestPropertySource#properties + */ + String[] getProperties() { + return this.properties; + } + + /** + * Get the {@code inheritProperties} flag that was declared via {@code @TestPropertySource}. + * + * @return the {@code inheritProperties} flag + * @see TestPropertySource#inheritProperties + */ + boolean isInheritProperties() { + return this.inheritProperties; + } + + /** + * Provide a String representation of the {@code @TestPropertySource} + * attributes and declaring class. + */ + @Override + public String toString() { + return new ToStringCreator(this)// + .append("declaringClass", declaringClass.getName())// + .append("locations", ObjectUtils.nullSafeToString(locations))// + .append("inheritLocations", inheritLocations)// + .append("properties", ObjectUtils.nullSafeToString(properties))// + .append("inheritProperties", inheritProperties)// + .toString(); + } + + /** + * Resolve resource locations from the supplied {@code locations} and + * {@code value} arrays, which correspond to attributes of the same names in + * the {@link TestPropertySource} annotation. + * + * @throws IllegalStateException if both the locations and value attributes have been declared + */ + private static String[] resolveLocations(Class declaringClass, String[] locations, String[] value) { + Assert.notNull(declaringClass, "declaringClass must not be null"); + + if (!ObjectUtils.isEmpty(value) && !ObjectUtils.isEmpty(locations)) { + String msg = String.format("Class [%s] has been configured with @TestPropertySource's 'value' [%s] " + + "and 'locations' [%s] attributes. Only one declaration of resource " + + "locations is permitted per @TestPropertySource annotation.", declaringClass.getName(), + ObjectUtils.nullSafeToString(value), ObjectUtils.nullSafeToString(locations)); + logger.error(msg); + throw new IllegalStateException(msg); + } + else if (!ObjectUtils.isEmpty(value)) { + locations = value; + } + + return locations; + } + + /** + * Detect a default properties file for the supplied class, as specified + * in the class-level Javadoc for {@link TestPropertySource}. + */ + private static String detectDefaultPropertiesFile(Class testClass) { + String resourcePath = ClassUtils.convertClassNameToResourcePath(testClass.getName()) + ".properties"; + String prefixedResourcePath = ResourceUtils.CLASSPATH_URL_PREFIX + resourcePath; + ClassPathResource classPathResource = new ClassPathResource(resourcePath); + + if (classPathResource.exists()) { + if (logger.isInfoEnabled()) { + logger.info(String.format("Detected default properties file \"%s\" for test class [%s]", + prefixedResourcePath, testClass.getName())); + } + return prefixedResourcePath; + } + else { + String msg = String.format("Could not detect default properties file for test [%s]: " + + "%s does not exist. Either declare the 'locations' or 'properties' attributes " + + "of @TestPropertySource or make the default properties file available.", testClass.getName(), + classPathResource); + logger.error(msg); + throw new IllegalStateException(msg); + } + } + +} diff --git a/spring-test/src/main/java/org/springframework/test/context/support/TestPropertySourceUtils.java b/spring-test/src/main/java/org/springframework/test/context/support/TestPropertySourceUtils.java new file mode 100644 index 00000000000..8bc0360e5c7 --- /dev/null +++ b/spring-test/src/main/java/org/springframework/test/context/support/TestPropertySourceUtils.java @@ -0,0 +1,133 @@ +/* + * Copyright 2002-2014 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 + * + * http://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.support; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.core.annotation.AnnotationAttributes; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.util.TestContextResourceUtils; +import org.springframework.test.util.MetaAnnotationUtils.AnnotationDescriptor; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +import static org.springframework.test.util.MetaAnnotationUtils.*; + +/** + * Utility methods for working with {@link TestPropertySource @TestPropertySource}. + * + * @author Sam Brannen + * @since 4.1 + * @see TestPropertySource + */ +abstract class TestPropertySourceUtils { + + private static final Log logger = LogFactory.getLog(TestPropertySourceUtils.class); + + + private TestPropertySourceUtils() { + /* no-op */ + } + + static MergedTestPropertySources buildMergedTestPropertySources(Class testClass) { + Class annotationType = TestPropertySource.class; + AnnotationDescriptor descriptor = findAnnotationDescriptor(testClass, annotationType); + if (descriptor == null) { + return new MergedTestPropertySources(); + } + + // else... + List attributesList = resolveTestPropertySourceAttributes(testClass); + String[] locations = mergeLocations(attributesList); + String[] properties = mergeProperties(attributesList); + return new MergedTestPropertySources(locations, properties); + } + + private static List resolveTestPropertySourceAttributes(Class testClass) { + Assert.notNull(testClass, "Class must not be null"); + + final List attributesList = new ArrayList(); + final Class annotationType = TestPropertySource.class; + AnnotationDescriptor descriptor = findAnnotationDescriptor(testClass, annotationType); + Assert.notNull(descriptor, String.format( + "Could not find an 'annotation declaring class' for annotation type [%s] and class [%s]", + annotationType.getName(), testClass.getName())); + + while (descriptor != null) { + AnnotationAttributes annAttrs = descriptor.getAnnotationAttributes(); + Class rootDeclaringClass = descriptor.getRootDeclaringClass(); + + if (logger.isTraceEnabled()) { + logger.trace(String.format("Retrieved @TestPropertySource attributes [%s] for declaring class [%s].", + annAttrs, rootDeclaringClass.getName())); + } + + TestPropertySourceAttributes attributes = new TestPropertySourceAttributes(rootDeclaringClass, annAttrs); + if (logger.isTraceEnabled()) { + logger.trace("Resolved TestPropertySource attributes: " + attributes); + } + attributesList.add(attributes); + + descriptor = findAnnotationDescriptor(rootDeclaringClass.getSuperclass(), annotationType); + } + + return attributesList; + } + + private static String[] mergeLocations(List attributesList) { + final List locations = new ArrayList(); + + for (TestPropertySourceAttributes attrs : attributesList) { + if (logger.isTraceEnabled()) { + logger.trace(String.format("Processing locations for TestPropertySource attributes %s", attrs)); + } + + String[] locationsArray = TestContextResourceUtils.convertToClasspathResourcePaths( + attrs.getDeclaringClass(), attrs.getLocations()); + locations.addAll(0, Arrays. asList(locationsArray)); + + if (!attrs.isInheritLocations()) { + break; + } + } + + return StringUtils.toStringArray(locations); + } + + private static String[] mergeProperties(List attributesList) { + final List properties = new ArrayList(); + + for (TestPropertySourceAttributes attrs : attributesList) { + if (logger.isTraceEnabled()) { + logger.trace(String.format("Processing inlined properties for TestPropertySource attributes %s", attrs)); + } + + properties.addAll(0, Arrays. asList(attrs.getProperties())); + + if (!attrs.isInheritProperties()) { + break; + } + } + + return StringUtils.toStringArray(properties); + } + +} diff --git a/spring-test/src/main/java/org/springframework/test/context/web/WebMergedContextConfiguration.java b/spring-test/src/main/java/org/springframework/test/context/web/WebMergedContextConfiguration.java index 58e8b38607a..2a2aa5765e3 100644 --- a/spring-test/src/main/java/org/springframework/test/context/web/WebMergedContextConfiguration.java +++ b/spring-test/src/main/java/org/springframework/test/context/web/WebMergedContextConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2014 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. @@ -60,16 +60,9 @@ public class WebMergedContextConfiguration extends MergedContextConfiguration { /** * Create a new {@code WebMergedContextConfiguration} instance for the - * supplied test class, resource locations, annotated classes, context - * initializers, active profiles, resource base path, and {@code ContextLoader}. - * - *

If a {@code null} value is supplied for {@code locations}, - * {@code classes}, or {@code activeProfiles} an empty array will - * be stored instead. If a {@code null} value is supplied for the - * {@code contextInitializerClasses} an empty set will be stored instead. - * If an empty value is supplied for the {@code resourceBasePath} - * an empty string will be used. Furthermore, active profiles will be sorted, - * and duplicate profiles will be removed. + * supplied parameters. + *

Delegates to + * {@link #WebMergedContextConfiguration(Class, String[], Class[], Set, String[], String[], String[], String, ContextLoader, CacheAwareContextLoaderDelegate, MergedContextConfiguration)}. * * @param testClass the test class for which the configuration was merged * @param locations the merged resource locations @@ -90,18 +83,52 @@ public class WebMergedContextConfiguration extends MergedContextConfiguration { Set>> contextInitializerClasses, String[] activeProfiles, String resourceBasePath, ContextLoader contextLoader) { - this(testClass, locations, classes, contextInitializerClasses, activeProfiles, resourceBasePath, contextLoader, + this(testClass, locations, classes, contextInitializerClasses, activeProfiles, null, null, resourceBasePath, + contextLoader, null, null); } /** * Create a new {@code WebMergedContextConfiguration} instance for the - * supplied test class, resource locations, annotated classes, context - * initializers, active profiles, resource base path, and {@code ContextLoader}. + * supplied parameters. + *

Delegates to + * {@link #WebMergedContextConfiguration(Class, String[], Class[], Set, String[], String[], String[], String, ContextLoader, CacheAwareContextLoaderDelegate, MergedContextConfiguration)}. + * + * @param testClass the test class for which the configuration was merged + * @param locations the merged resource locations + * @param classes the merged annotated classes + * @param contextInitializerClasses the merged context initializer classes + * @param activeProfiles the merged active bean definition profiles + * @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 context + * @param parent the parent configuration or {@code null} if there is no parent + * @since 3.2.2 + * @deprecated as of Spring 4.1, use + * {@link #WebMergedContextConfiguration(Class, String[], Class[], Set, String[], String[], String[], String, ContextLoader, CacheAwareContextLoaderDelegate, MergedContextConfiguration)} + * instead. + */ + public WebMergedContextConfiguration( + Class testClass, + String[] locations, + Class[] classes, + Set>> contextInitializerClasses, + String[] activeProfiles, String resourceBasePath, ContextLoader contextLoader, + CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate, MergedContextConfiguration parent) { + + this(testClass, locations, classes, contextInitializerClasses, activeProfiles, null, null, resourceBasePath, + contextLoader, cacheAwareContextLoaderDelegate, parent); + } + + /** + * Create a new {@code WebMergedContextConfiguration} instance for the + * supplied parameters. * *

If a {@code null} value is supplied for {@code locations}, - * {@code classes}, or {@code activeProfiles} an empty array will - * be stored instead. If a {@code null} value is supplied for the + * {@code classes}, {@code activeProfiles}, {@code propertySourceLocations}, + * or {@code propertySourceProperties} an empty array will be stored instead. + * If a {@code null} value is supplied for the * {@code contextInitializerClasses} an empty set will be stored instead. * If an empty value is supplied for the {@code resourceBasePath} * an empty string will be used. Furthermore, active profiles will be sorted, @@ -112,23 +139,26 @@ public class WebMergedContextConfiguration extends MergedContextConfiguration { * @param classes the merged annotated classes * @param contextInitializerClasses the merged context initializer classes * @param activeProfiles the merged active bean definition profiles + * @param propertySourceLocations the merged {@code PropertySource} locations + * @param propertySourceProperties the merged {@code PropertySource} properties * @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 context * @param parent the parent configuration or {@code null} if there is no parent - * @since 3.2.2 + * @since 4.1 */ public WebMergedContextConfiguration( Class testClass, String[] locations, Class[] classes, Set>> contextInitializerClasses, - String[] activeProfiles, String resourceBasePath, ContextLoader contextLoader, + String[] activeProfiles, String[] propertySourceLocations, String[] propertySourceProperties, + String resourceBasePath, ContextLoader contextLoader, CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate, MergedContextConfiguration parent) { - super(testClass, locations, classes, contextInitializerClasses, activeProfiles, contextLoader, - cacheAwareContextLoaderDelegate, parent); + super(testClass, locations, classes, contextInitializerClasses, activeProfiles, propertySourceLocations, + propertySourceProperties, contextLoader, cacheAwareContextLoaderDelegate, parent); this.resourceBasePath = !StringUtils.hasText(resourceBasePath) ? "" : resourceBasePath; } @@ -185,6 +215,8 @@ public class WebMergedContextConfiguration extends MergedContextConfiguration { * {@linkplain #getLocations() locations}, {@linkplain #getClasses() annotated classes}, * {@linkplain #getContextInitializerClasses() context initializer classes}, * {@linkplain #getActiveProfiles() active profiles}, + * {@linkplain #getPropertySourceLocations() property source locations}, + * {@linkplain #getPropertySourceProperties() property source properties}, * {@linkplain #getResourceBasePath() resource base path}, the name of the * {@link #getContextLoader() ContextLoader}, and the * {@linkplain #getParent() parent configuration}. @@ -197,6 +229,8 @@ public class WebMergedContextConfiguration extends MergedContextConfiguration { .append("classes", ObjectUtils.nullSafeToString(getClasses()))// .append("contextInitializerClasses", ObjectUtils.nullSafeToString(getContextInitializerClasses()))// .append("activeProfiles", ObjectUtils.nullSafeToString(getActiveProfiles()))// + .append("propertySourceLocations", ObjectUtils.nullSafeToString(getPropertySourceLocations()))// + .append("propertySourceProperties", ObjectUtils.nullSafeToString(getPropertySourceProperties()))// .append("resourceBasePath", getResourceBasePath())// .append("contextLoader", nullSafeToString(getContextLoader()))// .append("parent", getParent())// diff --git a/spring-test/src/main/java/org/springframework/test/context/web/WebTestContextBootstrapper.java b/spring-test/src/main/java/org/springframework/test/context/web/WebTestContextBootstrapper.java index 46b0e4ad292..625739260b5 100644 --- a/spring-test/src/main/java/org/springframework/test/context/web/WebTestContextBootstrapper.java +++ b/spring-test/src/main/java/org/springframework/test/context/web/WebTestContextBootstrapper.java @@ -89,20 +89,23 @@ public class WebTestContextBootstrapper extends DefaultTestContextBootstrapper { String[] locations, Class[] classes, Set>> initializerClasses, - String[] activeProfiles, ContextLoader contextLoader, - CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate, MergedContextConfiguration parentConfig) { + String[] activeProfiles, String[] propertySourceLocations, String[] propertySourceProperties, + ContextLoader contextLoader, CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate, + MergedContextConfiguration parentConfig) { WebAppConfiguration webAppConfiguration = AnnotationUtils.findAnnotation(testClass, WebAppConfiguration.class); if (webAppConfiguration != null) { String resourceBasePath = webAppConfiguration.value(); return new WebMergedContextConfiguration(testClass, locations, classes, initializerClasses, activeProfiles, - resourceBasePath, contextLoader, cacheAwareContextLoaderDelegate, parentConfig); + propertySourceLocations, propertySourceProperties, resourceBasePath, contextLoader, + cacheAwareContextLoaderDelegate, parentConfig); } // else... return super.buildMergedContextConfiguration(testClass, locations, classes, initializerClasses, activeProfiles, - contextLoader, cacheAwareContextLoaderDelegate, parentConfig); + propertySourceLocations, propertySourceProperties, contextLoader, cacheAwareContextLoaderDelegate, + parentConfig); } } diff --git a/spring-test/src/test/java/org/springframework/test/context/env/ApplicationPropertyOverridePropertiesFileTestPropertySourceTests.java b/spring-test/src/test/java/org/springframework/test/context/env/ApplicationPropertyOverridePropertiesFileTestPropertySourceTests.java new file mode 100644 index 00000000000..af6cce319ae --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/env/ApplicationPropertyOverridePropertiesFileTestPropertySourceTests.java @@ -0,0 +1,64 @@ +/* + * Copyright 2002-2014 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 + * + * http://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.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.PropertySource; +import org.springframework.core.env.Environment; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import static org.junit.Assert.*; + +/** + * Integration tests for {@link TestPropertySource @TestPropertySource} + * support with an explicitly named properties file that overrides a + * application-level property configured via + * {@link PropertySource @PropertySource} on an + * {@link Configuration @Configuration} class. + * + * @author Sam Brannen + * @since 4.1 + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration +@TestPropertySource("ApplicationPropertyOverridePropertiesFileTestPropertySourceTests.properties") +public class ApplicationPropertyOverridePropertiesFileTestPropertySourceTests { + + @Autowired + protected Environment env; + + + @Test + public void verifyPropertiesAreAvailableInEnvironment() { + assertEquals("test override", env.getProperty("explicit")); + } + + + // ------------------------------------------------------------------- + + @Configuration + @PropertySource("classpath:/org/springframework/test/context/env/explicit.properties") + static class Config { + /* no user beans required for these tests */ + } + +} diff --git a/spring-test/src/test/java/org/springframework/test/context/env/ApplicationPropertyOverridePropertiesFileTestPropertySourceTests.properties b/spring-test/src/test/java/org/springframework/test/context/env/ApplicationPropertyOverridePropertiesFileTestPropertySourceTests.properties new file mode 100644 index 00000000000..1fafe0c4534 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/env/ApplicationPropertyOverridePropertiesFileTestPropertySourceTests.properties @@ -0,0 +1 @@ +explicit = test override \ No newline at end of file diff --git a/spring-test/src/test/java/org/springframework/test/context/env/DefaultPropertiesFileDetectionTestPropertySourceTests.java b/spring-test/src/test/java/org/springframework/test/context/env/DefaultPropertiesFileDetectionTestPropertySourceTests.java new file mode 100644 index 00000000000..5573a1658b6 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/env/DefaultPropertiesFileDetectionTestPropertySourceTests.java @@ -0,0 +1,64 @@ +/* + * Copyright 2002-2014 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 + * + * http://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.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.Environment; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import static org.junit.Assert.*; + +/** + * Integration tests that verify detection of a default properties file + * when {@link TestPropertySource @TestPropertySource} is empty. + * + * @author Sam Brannen + * @since 4.1 + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration +@TestPropertySource +public class DefaultPropertiesFileDetectionTestPropertySourceTests { + + @Autowired + protected Environment env; + + + @Test + public void verifyPropertiesAreAvailableInEnvironment() { + // from DefaultPropertiesFileDetectionTestPropertySourceTests.properties + assertEnvironmentValue("riddle", "auto detected"); + } + + protected void assertEnvironmentValue(String key, String expected) { + assertEquals("Value of key [" + key + "].", expected, env.getProperty(key)); + } + + + // ------------------------------------------------------------------- + + @Configuration + static class Config { + /* no user beans required for these tests */ + } + +} diff --git a/spring-test/src/test/java/org/springframework/test/context/env/DefaultPropertiesFileDetectionTestPropertySourceTests.properties b/spring-test/src/test/java/org/springframework/test/context/env/DefaultPropertiesFileDetectionTestPropertySourceTests.properties new file mode 100644 index 00000000000..ae10beee1cb --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/env/DefaultPropertiesFileDetectionTestPropertySourceTests.properties @@ -0,0 +1 @@ +riddle = auto detected \ No newline at end of file diff --git a/spring-test/src/test/java/org/springframework/test/context/env/ExplicitPropertiesFileTestPropertySourceTests.java b/spring-test/src/test/java/org/springframework/test/context/env/ExplicitPropertiesFileTestPropertySourceTests.java new file mode 100644 index 00000000000..9bc0f4e7a24 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/env/ExplicitPropertiesFileTestPropertySourceTests.java @@ -0,0 +1,61 @@ +/* + * Copyright 2002-2014 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 + * + * http://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.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.Environment; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import static org.junit.Assert.*; + +/** + * Integration tests for {@link TestPropertySource @TestPropertySource} + * support with an explicitly named properties file. + * + * @author Sam Brannen + * @since 4.1 + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration +@TestPropertySource("explicit.properties") +public class ExplicitPropertiesFileTestPropertySourceTests { + + @Autowired + protected Environment env; + + + @Test + public void verifyPropertiesAreAvailableInEnvironment() { + String userHomeKey = "user.home"; + assertEquals(System.getProperty(userHomeKey), env.getProperty(userHomeKey)); + assertEquals("enigma", env.getProperty("explicit")); + } + + + // ------------------------------------------------------------------- + + @Configuration + static class Config { + /* no user beans required for these tests */ + } + +} diff --git a/spring-test/src/test/java/org/springframework/test/context/env/ExtendedDefaultPropertiesFileDetectionTestPropertySourceTests.java b/spring-test/src/test/java/org/springframework/test/context/env/ExtendedDefaultPropertiesFileDetectionTestPropertySourceTests.java new file mode 100644 index 00000000000..106a1ff9375 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/env/ExtendedDefaultPropertiesFileDetectionTestPropertySourceTests.java @@ -0,0 +1,41 @@ +/* + * Copyright 2002-2014 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 + * + * http://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.Test; +import org.springframework.test.context.TestPropertySource; + +/** + * Integration tests that verify detection of default properties files + * when {@link TestPropertySource @TestPropertySource} is empty + * at multiple levels within a class hierarchy. + * + * @author Sam Brannen + * @since 4.1 + */ +@TestPropertySource +public class ExtendedDefaultPropertiesFileDetectionTestPropertySourceTests extends + DefaultPropertiesFileDetectionTestPropertySourceTests { + + @Test + public void verifyPropertiesAreAvailableInEnvironment() { + super.verifyPropertiesAreAvailableInEnvironment(); + // from ExtendedDefaultPropertiesFileDetectionTestPropertySourceTests.properties + assertEnvironmentValue("enigma", "auto detected"); + } + +} diff --git a/spring-test/src/test/java/org/springframework/test/context/env/ExtendedDefaultPropertiesFileDetectionTestPropertySourceTests.properties b/spring-test/src/test/java/org/springframework/test/context/env/ExtendedDefaultPropertiesFileDetectionTestPropertySourceTests.properties new file mode 100644 index 00000000000..b3abbd1ac45 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/env/ExtendedDefaultPropertiesFileDetectionTestPropertySourceTests.properties @@ -0,0 +1 @@ +enigma = auto detected \ No newline at end of file diff --git a/spring-test/src/test/java/org/springframework/test/context/env/InheritedRelativePathPropertiesFileTestPropertySourceTests.java b/spring-test/src/test/java/org/springframework/test/context/env/InheritedRelativePathPropertiesFileTestPropertySourceTests.java new file mode 100644 index 00000000000..83d093a3083 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/env/InheritedRelativePathPropertiesFileTestPropertySourceTests.java @@ -0,0 +1,34 @@ +/* + * Copyright 2002-2014 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 + * + * http://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.springframework.test.context.TestPropertySource; + +/** + * Integration tests for {@link TestPropertySource @TestPropertySource} + * support with an inherited explicitly named properties file that is + * referenced using a relative path. + * + * @author Sam Brannen + * @since 4.1 + */ +public class InheritedRelativePathPropertiesFileTestPropertySourceTests extends + ExplicitPropertiesFileTestPropertySourceTests { + + /* all tests are in superclass */ + +} diff --git a/spring-test/src/test/java/org/springframework/test/context/env/InlinedPropertiesTestPropertySourceTests.java b/spring-test/src/test/java/org/springframework/test/context/env/InlinedPropertiesTestPropertySourceTests.java new file mode 100644 index 00000000000..fd2ec5f50a3 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/env/InlinedPropertiesTestPropertySourceTests.java @@ -0,0 +1,60 @@ +/* + * Copyright 2002-2014 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 + * + * http://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.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.Environment; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import static org.junit.Assert.*; + +/** + * Integration tests for {@link TestPropertySource @TestPropertySource} + * support with an inlined properties. + * + * @author Sam Brannen + * @since 4.1 + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration +@TestPropertySource(properties = { "foo = bar", "enigma: 42" }) +public class InlinedPropertiesTestPropertySourceTests { + + @Autowired + protected Environment env; + + + @Test + public void verifyPropertiesAreAvailableInEnvironment() { + assertEquals("bar", env.getProperty("foo")); + assertEquals(42, env.getProperty("enigma", Integer.class).intValue()); + } + + + // ------------------------------------------------------------------- + + @Configuration + static class Config { + /* no user beans required for these tests */ + } + +} diff --git a/spring-test/src/test/java/org/springframework/test/context/env/MergedPropertiesFilesOverriddenByInlinedPropertiesTestPropertySourceTests.java b/spring-test/src/test/java/org/springframework/test/context/env/MergedPropertiesFilesOverriddenByInlinedPropertiesTestPropertySourceTests.java new file mode 100644 index 00000000000..3e0f404a615 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/env/MergedPropertiesFilesOverriddenByInlinedPropertiesTestPropertySourceTests.java @@ -0,0 +1,48 @@ +/* + * Copyright 2002-2014 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 + * + * http://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.Test; +import org.springframework.test.context.TestPropertySource; + +import static org.junit.Assert.*; + +/** + * Integration tests that verify support for overriding properties from + * properties files via inlined properties configured with + * {@link TestPropertySource @TestPropertySource}. + * + * @author Sam Brannen + * @since 4.1 + */ +@TestPropertySource(properties = { "explicit = inlined", "extended = inlined1", "extended = inlined2" }) +public class MergedPropertiesFilesOverriddenByInlinedPropertiesTestPropertySourceTests extends + MergedPropertiesFilesTestPropertySourceTests { + + @Test + @Override + public void verifyPropertiesAreAvailableInEnvironment() { + assertEquals("inlined", env.getProperty("explicit")); + } + + @Test + @Override + public void verifyExtendedPropertiesAreAvailableInEnvironment() { + assertEquals("inlined2", env.getProperty("extended")); + } + +} diff --git a/spring-test/src/test/java/org/springframework/test/context/env/MergedPropertiesFilesTestPropertySourceTests.java b/spring-test/src/test/java/org/springframework/test/context/env/MergedPropertiesFilesTestPropertySourceTests.java new file mode 100644 index 00000000000..1d8fa6f4beb --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/env/MergedPropertiesFilesTestPropertySourceTests.java @@ -0,0 +1,40 @@ +/* + * Copyright 2002-2014 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 + * + * http://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.Test; +import org.springframework.test.context.TestPropertySource; + +import static org.junit.Assert.*; + +/** + * Integration tests that verify support for contributing additional properties + * files to the Spring {@code Environment} via {@link TestPropertySource @TestPropertySource}. + * + * @author Sam Brannen + * @since 4.1 + */ +@TestPropertySource("extended.properties") +public class MergedPropertiesFilesTestPropertySourceTests extends + ExplicitPropertiesFileTestPropertySourceTests { + + @Test + public void verifyExtendedPropertiesAreAvailableInEnvironment() { + assertEquals(42, env.getProperty("extended", Integer.class).intValue()); + } + +} diff --git a/spring-test/src/test/java/org/springframework/test/context/env/SystemPropertyOverridePropertiesFileTestPropertySourceTests.java b/spring-test/src/test/java/org/springframework/test/context/env/SystemPropertyOverridePropertiesFileTestPropertySourceTests.java new file mode 100644 index 00000000000..41d4f05e414 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/env/SystemPropertyOverridePropertiesFileTestPropertySourceTests.java @@ -0,0 +1,74 @@ +/* + * Copyright 2002-2014 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 + * + * http://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.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.Environment; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import static org.junit.Assert.*; + +/** + * Integration tests for {@link TestPropertySource @TestPropertySource} + * support with an explicitly named properties file that overrides a + * system property. + * + * @author Sam Brannen + * @since 4.1 + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration +@TestPropertySource("SystemPropertyOverridePropertiesFileTestPropertySourceTests.properties") +public class SystemPropertyOverridePropertiesFileTestPropertySourceTests { + + private static final String KEY = SystemPropertyOverridePropertiesFileTestPropertySourceTests.class.getSimpleName() + ".riddle"; + + @Autowired + protected Environment env; + + + @BeforeClass + public static void setSystemProperty() { + System.setProperty(KEY, "override me!"); + } + + @AfterClass + public static void removeSystemProperty() { + System.setProperty(KEY, ""); + } + + @Test + public void verifyPropertiesAreAvailableInEnvironment() { + assertEquals("enigma", env.getProperty(KEY)); + } + + + // ------------------------------------------------------------------- + + @Configuration + static class Config { + /* no user beans required for these tests */ + } + +} diff --git a/spring-test/src/test/java/org/springframework/test/context/env/SystemPropertyOverridePropertiesFileTestPropertySourceTests.properties b/spring-test/src/test/java/org/springframework/test/context/env/SystemPropertyOverridePropertiesFileTestPropertySourceTests.properties new file mode 100644 index 00000000000..09c2b72e240 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/env/SystemPropertyOverridePropertiesFileTestPropertySourceTests.properties @@ -0,0 +1 @@ +SystemPropertyOverridePropertiesFileTestPropertySourceTests.riddle = enigma \ No newline at end of file diff --git a/spring-test/src/test/java/org/springframework/test/context/env/explicit.properties b/spring-test/src/test/java/org/springframework/test/context/env/explicit.properties new file mode 100644 index 00000000000..972c736192b --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/env/explicit.properties @@ -0,0 +1 @@ +explicit = enigma \ No newline at end of file diff --git a/spring-test/src/test/java/org/springframework/test/context/env/extended.properties b/spring-test/src/test/java/org/springframework/test/context/env/extended.properties new file mode 100644 index 00000000000..6a378359854 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/env/extended.properties @@ -0,0 +1 @@ +extended = 42 \ No newline at end of file diff --git a/spring-test/src/test/java/org/springframework/test/context/env/subpackage/SubpackageInheritedRelativePathPropertiesFileTestPropertySourceTests.java b/spring-test/src/test/java/org/springframework/test/context/env/subpackage/SubpackageInheritedRelativePathPropertiesFileTestPropertySourceTests.java new file mode 100644 index 00000000000..f5165636f15 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/env/subpackage/SubpackageInheritedRelativePathPropertiesFileTestPropertySourceTests.java @@ -0,0 +1,35 @@ +/* + * Copyright 2002-2014 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 + * + * http://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.subpackage; + +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.env.ExplicitPropertiesFileTestPropertySourceTests; + +/** + * Integration tests for {@link TestPropertySource @TestPropertySource} + * support with an inherited explicitly named properties file that is + * referenced using a relative path within a parent package. + * + * @author Sam Brannen + * @since 4.1 + */ +public class SubpackageInheritedRelativePathPropertiesFileTestPropertySourceTests extends + ExplicitPropertiesFileTestPropertySourceTests { + + /* all tests are in superclass */ + +} diff --git a/spring-test/src/test/java/org/springframework/test/context/support/TestPropertySourceUtilsTests.java b/spring-test/src/test/java/org/springframework/test/context/support/TestPropertySourceUtilsTests.java new file mode 100644 index 00000000000..5d5281baca2 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/support/TestPropertySourceUtilsTests.java @@ -0,0 +1,157 @@ +/* + * Copyright 2002-2014 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 + * + * http://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.support; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.springframework.test.context.TestPropertySource; + +import static org.hamcrest.CoreMatchers.*; +import static org.junit.Assert.*; +import static org.springframework.test.context.support.TestPropertySourceUtils.*; + +/** + * Unit tests for {@link TestPropertySourceUtils}. + * + * @author Sam Brannen + * @since 4.1 + */ +public class TestPropertySourceUtilsTests { + + private static final String[] EMPTY_STRING_ARRAY = new String[0]; + + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + + private void assertMergedTestPropertySources(Class testClass, String[] expectedLocations, + String[] expectedProperties) { + MergedTestPropertySources mergedPropertySources = buildMergedTestPropertySources(testClass); + assertNotNull(mergedPropertySources); + assertArrayEquals(expectedLocations, mergedPropertySources.getLocations()); + assertArrayEquals(expectedProperties, mergedPropertySources.getProperties()); + } + + @Test + public void emptyAnnotation() { + expectedException.expect(IllegalStateException.class); + expectedException.expectMessage(startsWith("Could not detect default properties file for test")); + expectedException.expectMessage(containsString("EmptyPropertySources.properties")); + buildMergedTestPropertySources(EmptyPropertySources.class); + } + + @Test + public void extendedEmptyAnnotation() { + expectedException.expect(IllegalStateException.class); + expectedException.expectMessage(startsWith("Could not detect default properties file for test")); + expectedException.expectMessage(containsString("ExtendedEmptyPropertySources.properties")); + buildMergedTestPropertySources(ExtendedEmptyPropertySources.class); + } + + @Test + public void value() { + assertMergedTestPropertySources(ValuePropertySources.class, new String[] { "classpath:/value.xml" }, + EMPTY_STRING_ARRAY); + } + + @Test + public void locationsAndValueAttributes() { + expectedException.expect(IllegalStateException.class); + buildMergedTestPropertySources(LocationsAndValuePropertySources.class); + } + + @Test + public void locationsAndProperties() { + assertMergedTestPropertySources(LocationsAndPropertiesPropertySources.class, new String[] { + "classpath:/foo1.xml", "classpath:/foo2.xml" }, new String[] { "k1a=v1a", "k1b: v1b" }); + } + + @Test + public void inheritedLocationsAndProperties() { + assertMergedTestPropertySources(InheritedPropertySources.class, new String[] { "classpath:/foo1.xml", + "classpath:/foo2.xml" }, new String[] { "k1a=v1a", "k1b: v1b" }); + } + + @Test + public void extendedLocationsAndProperties() { + assertMergedTestPropertySources(ExtendedPropertySources.class, new String[] { "classpath:/foo1.xml", + "classpath:/foo2.xml", "classpath:/bar1.xml", "classpath:/bar2.xml" }, new String[] { "k1a=v1a", + "k1b: v1b", "k2a v2a", "k2b: v2b" }); + } + + @Test + public void overriddenLocations() { + assertMergedTestPropertySources(OverriddenLocationsPropertySources.class, + new String[] { "classpath:/baz.properties" }, new String[] { "k1a=v1a", "k1b: v1b", "key = value" }); + } + + @Test + public void overriddenProperties() { + assertMergedTestPropertySources(OverriddenPropertiesPropertySources.class, new String[] { + "classpath:/foo1.xml", "classpath:/foo2.xml", "classpath:/baz.properties" }, new String[] { "key = value" }); + } + + @Test + public void overriddenLocationsAndProperties() { + assertMergedTestPropertySources(OverriddenLocationsAndPropertiesPropertySources.class, + new String[] { "classpath:/baz.properties" }, new String[] { "key = value" }); + } + + + // ------------------------------------------------------------------- + + @TestPropertySource + static class EmptyPropertySources { + } + + @TestPropertySource + static class ExtendedEmptyPropertySources extends EmptyPropertySources { + } + + @TestPropertySource(locations = "/foo", value = "/bar") + static class LocationsAndValuePropertySources { + } + + @TestPropertySource("/value.xml") + static class ValuePropertySources { + } + + @TestPropertySource(locations = { "/foo1.xml", "/foo2.xml" }, properties = { "k1a=v1a", "k1b: v1b" }) + static class LocationsAndPropertiesPropertySources { + } + + static class InheritedPropertySources extends LocationsAndPropertiesPropertySources { + } + + @TestPropertySource(locations = { "/bar1.xml", "/bar2.xml" }, properties = { "k2a v2a", "k2b: v2b" }) + static class ExtendedPropertySources extends LocationsAndPropertiesPropertySources { + } + + @TestPropertySource(locations = "/baz.properties", properties = "key = value", inheritLocations = false) + static class OverriddenLocationsPropertySources extends LocationsAndPropertiesPropertySources { + } + + @TestPropertySource(locations = "/baz.properties", properties = "key = value", inheritProperties = false) + static class OverriddenPropertiesPropertySources extends LocationsAndPropertiesPropertySources { + } + + @TestPropertySource(locations = "/baz.properties", properties = "key = value", inheritLocations = false, inheritProperties = false) + static class OverriddenLocationsAndPropertiesPropertySources extends LocationsAndPropertiesPropertySources { + } + +} diff --git a/spring-test/src/test/java/org/springframework/test/context/web/AnnotationConfigWebContextLoaderTests.java b/spring-test/src/test/java/org/springframework/test/context/web/AnnotationConfigWebContextLoaderTests.java index fed649315e5..8b78fa14645 100644 --- a/spring-test/src/test/java/org/springframework/test/context/web/AnnotationConfigWebContextLoaderTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/web/AnnotationConfigWebContextLoaderTests.java @@ -38,6 +38,7 @@ public class AnnotationConfigWebContextLoaderTests { @Test + @SuppressWarnings("deprecation") public void configMustNotContainLocations() throws Exception { expectedException.expect(IllegalStateException.class); expectedException.expectMessage(containsString("does not support resource locations")); diff --git a/spring-test/src/test/java/org/springframework/test/context/web/GenericXmlWebContextLoaderTests.java b/spring-test/src/test/java/org/springframework/test/context/web/GenericXmlWebContextLoaderTests.java index d8206e29166..4357ca752ab 100644 --- a/spring-test/src/test/java/org/springframework/test/context/web/GenericXmlWebContextLoaderTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/web/GenericXmlWebContextLoaderTests.java @@ -37,6 +37,7 @@ public class GenericXmlWebContextLoaderTests { @Test + @SuppressWarnings("deprecation") public void configMustNotContainAnnotatedClasses() throws Exception { expectedException.expect(IllegalStateException.class); expectedException.expectMessage(containsString("does not support annotated classes"));