Skip to content

Commit 2cf4147

Browse files
committed
Introduce @TestPropertySource support in the TCF
Spring Framework 3.1 introduced an Environment abstraction with support for hierarchical PropertySources that can be configured programmatically as well as declaratively via the @propertysource annotation. However, prior to this commit, there was no way to declaratively configure PropertySources in integration tests in the Spring TestContext Framework (TCF). This commit introduces declarative support for PropertySources in the TCF via a new class-level @TestPropertySource annotation. This annotation provides two options for declaring test property sources: - The 'locations' attribute allows developers to declare external resource locations for test properties files. - The 'properties' attribute allows developers to declare inlined properties in the form of key-value pairs. Test properties files are added to the Environment before all other property sources and can therefore override system and application property sources. Similarly, inlined properties are added to the Environment before all other property sources and can therefore override system property sources, application property sources, and test properties files. Specifically, this commit introduces the following major changes: - Introduced @TestPropertySource annotation along with internal TestPropertySourceAttributes, MergedTestPropertySources, and TestPropertySourceUtils for working with test property sources within the TCF. - All TestContextBootstrappers have been modified to support the merged property resource locations and inlined properties from @TestPropertySource. - MergedContextConfiguration (and consequently the context caching key) is now additionally based on the merged property resource locations and inlined properties from @TestPropertySource. The same applies to WebMergedContextConfiguration. - AbstractContextLoader's prepareContext() method now adds PropertySources for all resource locations and inlined properties from the supplied MergedContextConfiguration to the Environment of the supplied ApplicationContext. All subclasses of AbstractGenericContextLoader and AbstractGenericWebContextLoader therefore automatically provide support for @TestPropertySource. Issue: SPR-12051
1 parent 6665634 commit 2cf4147

File tree

30 files changed

+1686
-126
lines changed

30 files changed

+1686
-126
lines changed

spring-test/src/main/java/org/springframework/test/context/ContextConfiguration.java

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -53,22 +53,19 @@
5353
* The term <em>annotated class</em> can refer to any of the following.
5454
*
5555
* <ul>
56-
* <li>A class annotated with @{@link org.springframework.context.annotation.Configuration
57-
* Configuration}</li>
56+
* <li>A class annotated with {@link org.springframework.context.annotation.Configuration @Configuration}</li>
5857
* <li>A component (i.e., a class annotated with
5958
* {@link org.springframework.stereotype.Component @Component},
6059
* {@link org.springframework.stereotype.Service @Service},
6160
* {@link org.springframework.stereotype.Repository @Repository}, etc.)</li>
6261
* <li>A JSR-330 compliant class that is annotated with {@code javax.inject} annotations</li>
63-
* <li>Any other class that contains {@link org.springframework.context.annotation.Bean
64-
* @Bean}-methods</li>
62+
* <li>Any other class that contains {@link org.springframework.context.annotation.Bean @Bean}-methods</li>
6563
* </ul>
6664
*
6765
* <p>
68-
* Consult the Javadoc for {@link org.springframework.context.annotation.Configuration
69-
* @Configuration} and {@link org.springframework.context.annotation.Bean @Bean} for
70-
* further information regarding the configuration and semantics of
71-
* <em>annotated classes</em>.
66+
* Consult the Javadoc for {@link org.springframework.context.annotation.Configuration @Configuration}
67+
* and {@link org.springframework.context.annotation.Bean @Bean} for further
68+
* information regarding the configuration and semantics of <em>annotated classes</em>.
7269
*
7370
* <p>
7471
* As of Spring Framework 4.0, this annotation may be used as a <em>meta-annotation</em>
@@ -78,6 +75,7 @@
7875
* @since 2.5
7976
* @see ContextHierarchy
8077
* @see ActiveProfiles
78+
* @see TestPropertySource
8179
* @see ContextLoader
8280
* @see SmartContextLoader
8381
* @see ContextConfigurationAttributes

spring-test/src/main/java/org/springframework/test/context/MergedContextConfiguration.java

Lines changed: 116 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2013 the original author or authors.
2+
* Copyright 2002-2014 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -34,15 +34,17 @@
3434
/**
3535
* {@code MergedContextConfiguration} encapsulates the <em>merged</em>
3636
* context configuration declared on a test class and all of its superclasses
37-
* via {@link ContextConfiguration @ContextConfiguration} and
38-
* {@link ActiveProfiles @ActiveProfiles}.
37+
* via {@link ContextConfiguration @ContextConfiguration},
38+
* {@link ActiveProfiles @ActiveProfiles}, and
39+
* {@link TestPropertySource @TestPropertySource}.
3940
*
40-
* <p>Merged resource locations, annotated classes, and active profiles
41-
* represent all declared values in the test class hierarchy taking into
42-
* consideration the semantics of the
43-
* {@link ContextConfiguration#inheritLocations inheritLocations} and
44-
* {@link ActiveProfiles#inheritProfiles inheritProfiles} flags in
45-
* {@code @ContextConfiguration} and {@code @ActiveProfiles}, respectively.
41+
* <p>Merged context resource locations, annotated classes, active profiles,
42+
* property resource locations, and in-lined properties represent all declared
43+
* values in the test class hierarchy taking into consideration the semantics
44+
* of the {@link ContextConfiguration#inheritLocations},
45+
* {@link ActiveProfiles#inheritProfiles},
46+
* {@link TestPropertySource#inheritLocations}, and
47+
* {@link TestPropertySource#inheritProperties} flags.
4648
*
4749
* <p>A {@link SmartContextLoader} uses {@code MergedContextConfiguration}
4850
* to load an {@link org.springframework.context.ApplicationContext ApplicationContext}.
@@ -73,13 +75,15 @@ public class MergedContextConfiguration implements Serializable {
7375
private final Class<?>[] classes;
7476
private final Set<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> contextInitializerClasses;
7577
private final String[] activeProfiles;
78+
private final String[] propertySourceLocations;
79+
private final String[] propertySourceProperties;
7680
private final ContextLoader contextLoader;
7781
private final CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate;
7882
private final MergedContextConfiguration parent;
7983

8084

81-
private static String[] processLocations(String[] locations) {
82-
return locations == null ? EMPTY_STRING_ARRAY : locations;
85+
private static String[] processStrings(String[] array) {
86+
return array == null ? EMPTY_STRING_ARRAY : array;
8387
}
8488

8589
private static Class<?>[] processClasses(Class<?>[] classes) {
@@ -115,20 +119,15 @@ protected static String nullSafeToString(ContextLoader contextLoader) {
115119

116120
/**
117121
* Create a new {@code MergedContextConfiguration} instance for the
118-
* supplied test class, resource locations, annotated classes, active
119-
* profiles, and {@code ContextLoader}.
120-
*
121-
* <p>If a {@code null} value is supplied for {@code locations},
122-
* {@code classes}, or {@code activeProfiles} an empty array will
123-
* be stored instead. Furthermore, active profiles will be sorted, and duplicate
124-
* profiles will be removed.
122+
* supplied parameters.
123+
* <p>Delegates to
124+
* {@link #MergedContextConfiguration(Class, String[], Class[], Set, String[], String[], String[], ContextLoader, CacheAwareContextLoaderDelegate, MergedContextConfiguration)}.
125125
*
126126
* @param testClass the test class for which the configuration was merged
127-
* @param locations the merged resource locations
127+
* @param locations the merged context resource locations
128128
* @param classes the merged annotated classes
129129
* @param activeProfiles the merged active bean definition profiles
130130
* @param contextLoader the resolved {@code ContextLoader}
131-
* @see #MergedContextConfiguration(Class, String[], Class[], Set, String[], ContextLoader)
132131
*/
133132
public MergedContextConfiguration(Class<?> testClass, String[] locations, Class<?>[] classes,
134133
String[] activeProfiles, ContextLoader contextLoader) {
@@ -137,18 +136,12 @@ public MergedContextConfiguration(Class<?> testClass, String[] locations, Class<
137136

138137
/**
139138
* Create a new {@code MergedContextConfiguration} instance for the
140-
* supplied test class, resource locations, annotated classes, context
141-
* initializers, active profiles, and {@code ContextLoader}.
142-
*
143-
* <p>If a {@code null} value is supplied for {@code locations},
144-
* {@code classes}, or {@code activeProfiles} an empty array will
145-
* be stored instead. If a {@code null} value is supplied for the
146-
* {@code contextInitializerClasses} an empty set will be stored instead.
147-
* Furthermore, active profiles will be sorted, and duplicate profiles will
148-
* be removed.
139+
* supplied parameters.
140+
* <p>Delegates to
141+
* {@link #MergedContextConfiguration(Class, String[], Class[], Set, String[], String[], String[], ContextLoader, CacheAwareContextLoaderDelegate, MergedContextConfiguration)}.
149142
*
150143
* @param testClass the test class for which the configuration was merged
151-
* @param locations the merged resource locations
144+
* @param locations the merged context resource locations
152145
* @param classes the merged annotated classes
153146
* @param contextInitializerClasses the merged context initializer classes
154147
* @param activeProfiles the merged active bean definition profiles
@@ -166,19 +159,12 @@ public MergedContextConfiguration(
166159

167160
/**
168161
* Create a new {@code MergedContextConfiguration} instance for the
169-
* supplied test class, resource locations, annotated classes, context
170-
* initializers, active profiles, {@code ContextLoader}, and parent
171-
* configuration.
172-
*
173-
* <p>If a {@code null} value is supplied for {@code locations},
174-
* {@code classes}, or {@code activeProfiles} an empty array will
175-
* be stored instead. If a {@code null} value is supplied for the
176-
* {@code contextInitializerClasses} an empty set will be stored instead.
177-
* Furthermore, active profiles will be sorted, and duplicate profiles will
178-
* be removed.
162+
* supplied parameters.
163+
* <p>Delegates to
164+
* {@link #MergedContextConfiguration(Class, String[], Class[], Set, String[], String[], String[], ContextLoader, CacheAwareContextLoaderDelegate, MergedContextConfiguration)}.
179165
*
180166
* @param testClass the test class for which the configuration was merged
181-
* @param locations the merged resource locations
167+
* @param locations the merged context resource locations
182168
* @param classes the merged annotated classes
183169
* @param contextInitializerClasses the merged context initializer classes
184170
* @param activeProfiles the merged active bean definition profiles
@@ -195,11 +181,50 @@ public MergedContextConfiguration(
195181
Set<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> contextInitializerClasses,
196182
String[] activeProfiles, ContextLoader contextLoader,
197183
CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate, MergedContextConfiguration parent) {
184+
this(testClass, locations, classes, contextInitializerClasses, activeProfiles, null, null, contextLoader,
185+
cacheAwareContextLoaderDelegate, parent);
186+
}
187+
188+
/**
189+
* Create a new {@code MergedContextConfiguration} instance for the
190+
* supplied parameters.
191+
*
192+
* <p>If a {@code null} value is supplied for {@code locations},
193+
* {@code classes}, {@code activeProfiles}, {@code propertySourceLocations},
194+
* or {@code propertySourceProperties} an empty array will be stored instead.
195+
* If a {@code null} value is supplied for the
196+
* {@code contextInitializerClasses} an empty set will be stored instead.
197+
* Furthermore, active profiles will be sorted, and duplicate profiles
198+
* will be removed.
199+
*
200+
* @param testClass the test class for which the configuration was merged
201+
* @param locations the merged context resource locations
202+
* @param classes the merged annotated classes
203+
* @param contextInitializerClasses the merged context initializer classes
204+
* @param activeProfiles the merged active bean definition profiles
205+
* @param propertySourceLocations the merged {@code PropertySource} locations
206+
* @param propertySourceProperties the merged {@code PropertySource} properties
207+
* @param contextLoader the resolved {@code ContextLoader}
208+
* @param cacheAwareContextLoaderDelegate a cache-aware context loader
209+
* delegate with which to retrieve the parent context
210+
* @param parent the parent configuration or {@code null} if there is no parent
211+
* @since 4.1
212+
*/
213+
public MergedContextConfiguration(
214+
Class<?> testClass,
215+
String[] locations,
216+
Class<?>[] classes,
217+
Set<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> contextInitializerClasses,
218+
String[] activeProfiles, String[] propertySourceLocations, String[] propertySourceProperties,
219+
ContextLoader contextLoader, CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate,
220+
MergedContextConfiguration parent) {
198221
this.testClass = testClass;
199-
this.locations = processLocations(locations);
222+
this.locations = processStrings(locations);
200223
this.classes = processClasses(classes);
201224
this.contextInitializerClasses = processContextInitializerClasses(contextInitializerClasses);
202225
this.activeProfiles = processActiveProfiles(activeProfiles);
226+
this.propertySourceLocations = processStrings(propertySourceLocations);
227+
this.propertySourceProperties = processStrings(propertySourceProperties);
203228
this.contextLoader = contextLoader;
204229
this.cacheAwareContextLoaderDelegate = cacheAwareContextLoaderDelegate;
205230
this.parent = parent;
@@ -213,7 +238,10 @@ public Class<?> getTestClass() {
213238
}
214239

215240
/**
216-
* Get the merged resource locations for the {@linkplain #getTestClass() test class}.
241+
* Get the merged resource locations for {@code ApplicationContext}
242+
* configuration files for the {@linkplain #getTestClass() test class}.
243+
* <p>Context resource locations typically represent XML configuration
244+
* files or Groovy scripts.
217245
*/
218246
public String[] getLocations() {
219247
return locations;
@@ -228,7 +256,7 @@ public Class<?>[] getClasses() {
228256

229257
/**
230258
* Determine if this {@code MergedContextConfiguration} instance has
231-
* path-based resource locations.
259+
* path-based context resource locations.
232260
*
233261
* @return {@code true} if the {@link #getLocations() locations} array is not empty
234262
* @since 4.0.4
@@ -254,7 +282,7 @@ public boolean hasClasses() {
254282

255283
/**
256284
* Determine if this {@code MergedContextConfiguration} instance has
257-
* either path-based resource locations or class-based resources.
285+
* either path-based context resource locations or class-based resources.
258286
*
259287
* @return {@code true} if either the {@link #getLocations() locations}
260288
* or the {@link #getClasses() classes} array is not empty
@@ -275,12 +303,36 @@ public Set<Class<? extends ApplicationContextInitializer<? extends ConfigurableA
275303
}
276304

277305
/**
278-
* Get the merged active bean definition profiles for the {@linkplain #getTestClass() test class}.
306+
* Get the merged active bean definition profiles for the
307+
* {@linkplain #getTestClass() test class}.
308+
* @see ActiveProfiles
279309
*/
280310
public String[] getActiveProfiles() {
281311
return activeProfiles;
282312
}
283313

314+
/**
315+
* Get the merged resource locations for test {@code PropertySources} for the
316+
* {@linkplain #getTestClass() test class}.
317+
* @see TestPropertySource#locations
318+
* @see java.util.Properties
319+
*/
320+
public String[] getPropertySourceLocations() {
321+
return propertySourceLocations;
322+
}
323+
324+
/**
325+
* Get the merged test {@code PropertySource} properties for the
326+
* {@linkplain #getTestClass() test class}.
327+
* <p>Properties will be loaded into the {@code Environment}'s set of
328+
* {@code PropertySources}.
329+
* @see TestPropertySource#properties
330+
* @see java.util.Properties
331+
*/
332+
public String[] getPropertySourceProperties() {
333+
return propertySourceProperties;
334+
}
335+
284336
/**
285337
* Get the resolved {@link ContextLoader} for the {@linkplain #getTestClass() test class}.
286338
*/
@@ -334,6 +386,8 @@ public int hashCode() {
334386
result = prime * result + Arrays.hashCode(classes);
335387
result = prime * result + contextInitializerClasses.hashCode();
336388
result = prime * result + Arrays.hashCode(activeProfiles);
389+
result = prime * result + Arrays.hashCode(propertySourceLocations);
390+
result = prime * result + Arrays.hashCode(propertySourceProperties);
337391
result = prime * result + (parent == null ? 0 : parent.hashCode());
338392
result = prime * result + nullSafeToString(contextLoader).hashCode();
339393
return result;
@@ -345,6 +399,8 @@ public int hashCode() {
345399
* {@linkplain #getClasses() annotated classes},
346400
* {@linkplain #getContextInitializerClasses() context initializer classes},
347401
* {@linkplain #getActiveProfiles() active profiles},
402+
* {@linkplain #getPropertySourceLocations() property source locations},
403+
* {@linkplain #getPropertySourceProperties() property source properties},
348404
* {@linkplain #getParent() parents}, and the fully qualified names of their
349405
* {@link #getContextLoader() ContextLoaders}.
350406
*/
@@ -376,6 +432,14 @@ public boolean equals(Object obj) {
376432
return false;
377433
}
378434

435+
if (!Arrays.equals(this.propertySourceLocations, that.propertySourceLocations)) {
436+
return false;
437+
}
438+
439+
if (!Arrays.equals(this.propertySourceProperties, that.propertySourceProperties)) {
440+
return false;
441+
}
442+
379443
if (this.parent == null) {
380444
if (that.parent != null) {
381445
return false;
@@ -396,8 +460,10 @@ else if (!this.parent.equals(that.parent)) {
396460
* Provide a String representation of the {@linkplain #getTestClass() test class},
397461
* {@linkplain #getLocations() locations}, {@linkplain #getClasses() annotated classes},
398462
* {@linkplain #getContextInitializerClasses() context initializer classes},
399-
* {@linkplain #getActiveProfiles() active profiles}, the name of the
400-
* {@link #getContextLoader() ContextLoader}, and the
463+
* {@linkplain #getActiveProfiles() active profiles},
464+
* {@linkplain #getPropertySourceLocations() property source locations},
465+
* {@linkplain #getPropertySourceProperties() property source properties},
466+
* the name of the {@link #getContextLoader() ContextLoader}, and the
401467
* {@linkplain #getParent() parent configuration}.
402468
*/
403469
@Override
@@ -408,6 +474,8 @@ public String toString() {
408474
.append("classes", ObjectUtils.nullSafeToString(classes))//
409475
.append("contextInitializerClasses", ObjectUtils.nullSafeToString(contextInitializerClasses))//
410476
.append("activeProfiles", ObjectUtils.nullSafeToString(activeProfiles))//
477+
.append("propertySourceLocations", ObjectUtils.nullSafeToString(propertySourceLocations))//
478+
.append("propertySourceProperties", ObjectUtils.nullSafeToString(propertySourceProperties))//
411479
.append("contextLoader", nullSafeToString(contextLoader))//
412480
.append("parent", parent)//
413481
.toString();

0 commit comments

Comments
 (0)