Skip to content

Commit 989f619

Browse files
committed
Support classes AND locations in @ContextConfiguration
Prior to this commit, the Spring TestContext Framework did not support the declaration of both 'locations' and 'classes' within @ContextConfiguration at the same time. This commit addresses this in the following manner: - ContextConfigurationAttributes no longer throws an IllegalArgumentException if both 'locations' and 'classes' are supplied to its constructor. - Concrete SmartContextLoader implementations now validate the supplied MergedContextConfiguration before attempting to load the ApplicationContext. See validateMergedContextConfiguration(). - Introduced tests for hybrid context loaders like the one used in Spring Boot. See HybridContextLoaderTests. - Updated the Testing chapter of the reference manual so that it no longer states that locations and classes cannot be used simultaneously, mentioning Spring Boot as well. - The Javadoc for @ContextConfiguration has been updated accordingly. - Added hasLocations(), hasClasses(), and hasResources() convenience methods to MergedContextConfiguration. Issue: SPR-11634 (cherry picked from commit 1f017c4)
1 parent 17bf5b9 commit 989f619

22 files changed

+650
-61
lines changed

spring-test/.springBeans

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<?xml version="1.0" encoding="UTF-8"?>
22
<beansProjectDescription>
33
<version>1</version>
4-
<pluginVersion><![CDATA[3.1.0.201210040510-RELEASE]]></pluginVersion>
4+
<pluginVersion><![CDATA[3.5.0.201402030809-M2]]></pluginVersion>
55
<configSuffixes>
66
<configSuffix><![CDATA[xml]]></configSuffix>
77
</configSuffixes>
@@ -11,7 +11,10 @@
1111
<config>src/test/java/org/springframework/test/context/junit4/aci/xml/MultipleInitializersXmlConfigTests-context.xml</config>
1212
<config>src/test/resources/org/springframework/test/context/web/RequestAndSessionScopedBeansWacTests-context.xml</config>
1313
<config>src/test/resources/org/springframework/test/context/hierarchies/web/DispatcherWacRootWacEarTests-context.xml</config>
14+
<config>src/test/java/org/springframework/test/context/junit4/hybrid/hybrid-config.xml</config>
1415
</configs>
16+
<autoconfigs>
17+
</autoconfigs>
1518
<configSets>
1619
</configSets>
1720
</beansProjectDescription>

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

Lines changed: 11 additions & 13 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.
@@ -36,11 +36,14 @@
3636
*
3737
* <p>Prior to Spring 3.1, only path-based resource locations were supported.
3838
* As of Spring 3.1, {@linkplain #loader context loaders} may choose to support
39-
* either path-based or class-based resources (but not both). Consequently
39+
* <em>either</em> path-based <em>or</em> class-based resources. As of Spring
40+
* 4.0.4, {@linkplain #loader context loaders} may choose to support path-based
41+
* <em>and</em> class-based resources simultaneously. Consequently
4042
* {@code @ContextConfiguration} can be used to declare either path-based
41-
* resource locations (via the {@link #locations} or {@link #value}
42-
* attribute) <i>or</i> annotated classes (via the {@link #classes}
43-
* attribute).
43+
* resource locations (via the {@link #locations} or {@link #value} attribute)
44+
* <em>or</em> annotated classes (via the {@link #classes} attribute). Note,
45+
* however, that most implementations of {@link SmartContextLoader} only support
46+
* a single resource type.
4447
*
4548
* <h3>Annotated Classes</h3>
4649
*
@@ -86,9 +89,8 @@
8689
/**
8790
* Alias for {@link #locations}.
8891
*
89-
* <p>This attribute may <strong>not</strong> be used in conjunction
90-
* with {@link #locations} or {@link #classes}, but it may be used
91-
* instead of {@link #locations}.
92+
* <p>This attribute may <strong>not</strong> be used in conjunction with
93+
* {@link #locations}, but it may be used instead of {@link #locations}.
9294
* @since 3.0
9395
* @see #inheritLocations
9496
*/
@@ -117,8 +119,7 @@
117119
* loaders.
118120
*
119121
* <p>This attribute may <strong>not</strong> be used in conjunction with
120-
* {@link #value} or {@link #classes}, but it may be used instead of
121-
* {@link #value}.
122+
* {@link #value}, but it may be used instead of {@link #value}.
122123
* @since 2.5
123124
* @see #inheritLocations
124125
*/
@@ -135,9 +136,6 @@
135136
* <em>annotated classes</em> are specified. See the documentation for
136137
* {@link #loader} for further details regarding default loaders.
137138
*
138-
* <p>This attribute may <strong>not</strong> be used in conjunction with
139-
* {@link #locations} or {@link #value}.
140-
*
141139
* @since 3.1
142140
* @see org.springframework.context.annotation.Configuration
143141
* @see org.springframework.test.context.support.AnnotationConfigContextLoader

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

Lines changed: 15 additions & 16 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.
@@ -29,9 +29,8 @@
2929
import org.springframework.util.StringUtils;
3030

3131
/**
32-
* {@code ContextConfigurationAttributes} encapsulates the context
33-
* configuration attributes declared on a test class via
34-
* {@link ContextConfiguration @ContextConfiguration}.
32+
* {@code ContextConfigurationAttributes} encapsulates the context configuration
33+
* attributes declared via {@link ContextConfiguration @ContextConfiguration}.
3534
*
3635
* @author Sam Brannen
3736
* @since 3.1
@@ -111,8 +110,9 @@ public ContextConfigurationAttributes(Class<?> declaringClass, ContextConfigurat
111110

112111
/**
113112
* Construct a new {@link ContextConfigurationAttributes} instance for the
114-
* supplied {@link ContextConfiguration @ContextConfiguration} annotation and
115-
* the {@linkplain Class test class} that declared it.
113+
* supplied {@link AnnotationAttributes} (parsed from a
114+
* {@link ContextConfiguration @ContextConfiguration} annotation) and
115+
* the {@linkplain Class test class} that declared them.
116116
* @param declaringClass the test class that declared {@code @ContextConfiguration}
117117
* @param annAttrs the annotation attributes from which to retrieve the attributes
118118
*/
@@ -140,7 +140,7 @@ public ContextConfigurationAttributes(Class<?> declaringClass, AnnotationAttribu
140140
* @param inheritLocations the {@code inheritLocations} flag declared via {@code @ContextConfiguration}
141141
* @param contextLoaderClass the {@code ContextLoader} class declared via {@code @ContextConfiguration}
142142
* @throws IllegalArgumentException if the {@code declaringClass} or {@code contextLoaderClass} is
143-
* {@code null}, or if the {@code locations} and {@code classes} are both non-empty
143+
* {@code null}
144144
* @deprecated as of Spring 3.2, use
145145
* {@link #ContextConfigurationAttributes(Class, String[], Class[], boolean, Class[], boolean, String, Class)}
146146
* instead
@@ -165,7 +165,7 @@ public ContextConfigurationAttributes(Class<?> declaringClass, String[] location
165165
* @param inheritInitializers the {@code inheritInitializers} flag declared via {@code @ContextConfiguration}
166166
* @param contextLoaderClass the {@code ContextLoader} class declared via {@code @ContextConfiguration}
167167
* @throws IllegalArgumentException if the {@code declaringClass} or {@code contextLoaderClass} is
168-
* {@code null}, or if the {@code locations} and {@code classes} are both non-empty
168+
* {@code null}
169169
*/
170170
public ContextConfigurationAttributes(Class<?> declaringClass, String[] locations, Class<?>[] classes,
171171
boolean inheritLocations,
@@ -190,7 +190,7 @@ public ContextConfigurationAttributes(Class<?> declaringClass, String[] location
190190
* @param name the name of level in the context hierarchy, or {@code null} if not applicable
191191
* @param contextLoaderClass the {@code ContextLoader} class declared via {@code @ContextConfiguration}
192192
* @throws IllegalArgumentException if the {@code declaringClass} or {@code contextLoaderClass} is
193-
* {@code null}, or if the {@code locations} and {@code classes} are both non-empty
193+
* {@code null}
194194
*/
195195
public ContextConfigurationAttributes(Class<?> declaringClass, String[] locations, Class<?>[] classes,
196196
boolean inheritLocations,
@@ -200,14 +200,13 @@ public ContextConfigurationAttributes(Class<?> declaringClass, String[] location
200200
Assert.notNull(declaringClass, "declaringClass must not be null");
201201
Assert.notNull(contextLoaderClass, "contextLoaderClass must not be null");
202202

203-
if (!ObjectUtils.isEmpty(locations) && !ObjectUtils.isEmpty(classes)) {
204-
String msg = String.format(
203+
if (!ObjectUtils.isEmpty(locations) && !ObjectUtils.isEmpty(classes) && logger.isDebugEnabled()) {
204+
logger.debug(String.format(
205205
"Test class [%s] has been configured with @ContextConfiguration's 'locations' (or 'value') %s "
206-
+ "and 'classes' %s attributes. Only one declaration of resources "
207-
+ "is permitted per @ContextConfiguration annotation.", declaringClass.getName(),
208-
ObjectUtils.nullSafeToString(locations), ObjectUtils.nullSafeToString(classes));
209-
logger.error(msg);
210-
throw new IllegalArgumentException(msg);
206+
+ "and 'classes' %s attributes. Most SmartContextLoader implementations support "
207+
+ "only one declaration of resources per @ContextConfiguration annotation.",
208+
declaringClass.getName(), ObjectUtils.nullSafeToString(locations),
209+
ObjectUtils.nullSafeToString(classes)));
211210
}
212211

213212
this.declaringClass = declaringClass;

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

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,46 @@ public Class<?>[] getClasses() {
226226
return classes;
227227
}
228228

229+
/**
230+
* Determine if this {@code MergedContextConfiguration} instance has
231+
* path-based resource locations.
232+
*
233+
* @return {@code true} if the {@link #getLocations() locations} array is not empty
234+
* @since 4.0.4
235+
* @see #hasResources()
236+
* @see #hasClasses()
237+
*/
238+
public boolean hasLocations() {
239+
return !ObjectUtils.isEmpty(getLocations());
240+
}
241+
242+
/**
243+
* Determine if this {@code MergedContextConfiguration} instance has
244+
* class-based resources.
245+
*
246+
* @return {@code true} if the {@link #getClasses() classes} array is not empty
247+
* @since 4.0.4
248+
* @see #hasResources()
249+
* @see #hasLocations()
250+
*/
251+
public boolean hasClasses() {
252+
return !ObjectUtils.isEmpty(getClasses());
253+
}
254+
255+
/**
256+
* Determine if this {@code MergedContextConfiguration} instance has
257+
* either path-based resource locations or class-based resources.
258+
*
259+
* @return {@code true} if either the {@link #getLocations() locations}
260+
* or the {@link #getClasses() classes} array is not empty
261+
* @since 4.0.4
262+
* @see #hasLocations()
263+
* @see #hasClasses()
264+
*/
265+
public boolean hasResources() {
266+
return hasLocations() || hasClasses();
267+
}
268+
229269
/**
230270
* Get the merged {@code ApplicationContextInitializer} classes for the
231271
* {@linkplain #getTestClass() test class}.

spring-test/src/main/java/org/springframework/test/context/support/AbstractGenericContextLoader.java

Lines changed: 19 additions & 1 deletion
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.
@@ -68,6 +68,8 @@ public abstract class AbstractGenericContextLoader extends AbstractContextLoader
6868
* <p>Implementation details:
6969
*
7070
* <ul>
71+
* <li>Calls {@link #validateMergedContextConfiguration(MergedContextConfiguration)}
72+
* to allow subclasses to validate the supplied configuration before proceeding.</li>
7173
* <li>Creates a {@link GenericApplicationContext} instance.</li>
7274
* <li>If the supplied {@code MergedContextConfiguration} references a
7375
* {@linkplain MergedContextConfiguration#getParent() parent configuration},
@@ -106,6 +108,8 @@ public final ConfigurableApplicationContext loadContext(MergedContextConfigurati
106108
mergedConfig));
107109
}
108110

111+
validateMergedContextConfiguration(mergedConfig);
112+
109113
GenericApplicationContext context = new GenericApplicationContext();
110114

111115
ApplicationContext parent = mergedConfig.getParentApplicationContext();
@@ -123,6 +127,20 @@ public final ConfigurableApplicationContext loadContext(MergedContextConfigurati
123127
return context;
124128
}
125129

130+
/**
131+
* Validate the supplied {@link MergedContextConfiguration} with respect to
132+
* what this context loader supports.
133+
* <p>The default implementation is a <em>no-op</em> but can be overridden by
134+
* subclasses as appropriate.
135+
* @param mergedConfig the merged configuration to validate
136+
* @throws IllegalStateException if the supplied configuration is not valid
137+
* for this context loader
138+
* @since 4.0.4
139+
*/
140+
protected void validateMergedContextConfiguration(MergedContextConfiguration mergedConfig) {
141+
/* no-op */
142+
}
143+
126144
/**
127145
* Load a Spring ApplicationContext from the supplied {@code locations}.
128146
*

spring-test/src/main/java/org/springframework/test/context/support/AnnotationConfigContextLoader.java

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2012 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.
@@ -76,9 +76,8 @@ public class AnnotationConfigContextLoader extends AbstractGenericContextLoader
7676
*/
7777
@Override
7878
public void processContextConfiguration(ContextConfigurationAttributes configAttributes) {
79-
if (ObjectUtils.isEmpty(configAttributes.getClasses()) && isGenerateDefaultLocations()) {
80-
Class<?>[] defaultConfigClasses = detectDefaultConfigurationClasses(configAttributes.getDeclaringClass());
81-
configAttributes.setClasses(defaultConfigClasses);
79+
if (!configAttributes.hasClasses() && isGenerateDefaultLocations()) {
80+
configAttributes.setClasses(detectDefaultConfigurationClasses(configAttributes.getDeclaringClass()));
8281
}
8382
}
8483

@@ -148,6 +147,24 @@ protected String getResourceSuffix() {
148147

149148
// --- AbstractGenericContextLoader ----------------------------------------
150149

150+
/**
151+
* Ensure that the supplied {@link MergedContextConfiguration} does not
152+
* contain {@link MergedContextConfiguration#getLocations() locations}.
153+
* @since 4.0.4
154+
* @see AbstractGenericContextLoader#validateMergedContextConfiguration
155+
*/
156+
@Override
157+
protected void validateMergedContextConfiguration(MergedContextConfiguration mergedConfig) {
158+
if (mergedConfig.hasLocations()) {
159+
String msg = String.format(
160+
"Test class [%s] has been configured with @ContextConfiguration's 'locations' (or 'value') attribute %s, "
161+
+ "but %s does not support resource locations.", mergedConfig.getTestClass().getName(),
162+
ObjectUtils.nullSafeToString(mergedConfig.getLocations()), getClass().getSimpleName());
163+
logger.error(msg);
164+
throw new IllegalStateException(msg);
165+
}
166+
}
167+
151168
/**
152169
* Register classes in the supplied {@link GenericApplicationContext context}
153170
* from the classes in the supplied {@link MergedContextConfiguration}.

spring-test/src/main/java/org/springframework/test/context/support/AnnotationConfigContextLoaderUtils.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2012 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.
@@ -83,7 +83,7 @@ private static boolean isDefaultConfigurationClassCandidate(Class<?> clazz) {
8383
* configuration classes that comply with the constraints required of
8484
* {@code @Configuration} class implementations. If a potential candidate
8585
* configuration class does not meet these requirements, this method will log a
86-
* warning, and the potential candidate class will be ignored.
86+
* debug message, and the potential candidate class will be ignored.
8787
* @param declaringClass the test class that declared {@code @ContextConfiguration}
8888
* @return an array of default configuration classes, potentially empty but
8989
* never {@code null}

spring-test/src/main/java/org/springframework/test/context/support/GenericPropertiesContextLoader.java

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2012 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.
@@ -21,6 +21,8 @@
2121
import org.springframework.beans.factory.support.BeanDefinitionReader;
2222
import org.springframework.beans.factory.support.PropertiesBeanDefinitionReader;
2323
import org.springframework.context.support.GenericApplicationContext;
24+
import org.springframework.test.context.MergedContextConfiguration;
25+
import org.springframework.util.ObjectUtils;
2426

2527
/**
2628
* Concrete implementation of {@link AbstractGenericContextLoader} that reads
@@ -45,8 +47,26 @@ protected BeanDefinitionReader createBeanDefinitionReader(final GenericApplicati
4547
* Returns &quot;{@code -context.properties}&quot;.
4648
*/
4749
@Override
48-
public String getResourceSuffix() {
50+
protected String getResourceSuffix() {
4951
return "-context.properties";
5052
}
5153

54+
/**
55+
* Ensure that the supplied {@link MergedContextConfiguration} does not
56+
* contain {@link MergedContextConfiguration#getClasses() classes}.
57+
* @since 4.0.4
58+
* @see AbstractGenericContextLoader#validateMergedContextConfiguration
59+
*/
60+
@Override
61+
protected void validateMergedContextConfiguration(MergedContextConfiguration mergedConfig) {
62+
if (mergedConfig.hasClasses()) {
63+
String msg = String.format(
64+
"Test class [%s] has been configured with @ContextConfiguration's 'classes' attribute %s, "
65+
+ "but %s does not support annotated classes.", mergedConfig.getTestClass().getName(),
66+
ObjectUtils.nullSafeToString(mergedConfig.getClasses()), getClass().getSimpleName());
67+
logger.error(msg);
68+
throw new IllegalStateException(msg);
69+
}
70+
}
71+
5272
}

spring-test/src/main/java/org/springframework/test/context/support/GenericXmlContextLoader.java

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2012 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.
@@ -19,6 +19,8 @@
1919
import org.springframework.beans.factory.support.BeanDefinitionReader;
2020
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
2121
import org.springframework.context.support.GenericApplicationContext;
22+
import org.springframework.test.context.MergedContextConfiguration;
23+
import org.springframework.util.ObjectUtils;
2224

2325
/**
2426
* Concrete implementation of {@link AbstractGenericContextLoader} that reads
@@ -43,8 +45,26 @@ protected BeanDefinitionReader createBeanDefinitionReader(final GenericApplicati
4345
* Returns &quot;{@code -context.xml}&quot;.
4446
*/
4547
@Override
46-
public String getResourceSuffix() {
48+
protected String getResourceSuffix() {
4749
return "-context.xml";
4850
}
4951

52+
/**
53+
* Ensure that the supplied {@link MergedContextConfiguration} does not
54+
* contain {@link MergedContextConfiguration#getClasses() classes}.
55+
* @since 4.0.4
56+
* @see AbstractGenericContextLoader#validateMergedContextConfiguration
57+
*/
58+
@Override
59+
protected void validateMergedContextConfiguration(MergedContextConfiguration mergedConfig) {
60+
if (mergedConfig.hasClasses()) {
61+
String msg = String.format(
62+
"Test class [%s] has been configured with @ContextConfiguration's 'classes' attribute %s, "
63+
+ "but %s does not support annotated classes.", mergedConfig.getTestClass().getName(),
64+
ObjectUtils.nullSafeToString(mergedConfig.getClasses()), getClass().getSimpleName());
65+
logger.error(msg);
66+
throw new IllegalStateException(msg);
67+
}
68+
}
69+
5070
}

0 commit comments

Comments
 (0)