Skip to content

Commit 181299c

Browse files
committed
Improve ex msg when locations & classes are declared in test hierarchy
Prior to this commit, if both locations and classes were declared via @ContextConfiguration at differing levels in a test class hierarchy, the exception message stated that neither of the default context loaders was able to load an ApplicationContext from the merged context configuration, but the message didn't explain why. This commit adds an explicit check for such scenarios and provides a more informative exception message similar to the following: "Neither X nor Y supports loading an ApplicationContext from [MergedContextConfiguration ...]: declare either 'locations' or 'classes' but not both." Issue: SPR-12060
1 parent f4c23d8 commit 181299c

File tree

2 files changed

+57
-25
lines changed

2 files changed

+57
-25
lines changed

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

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121

2222
import org.apache.commons.logging.Log;
2323
import org.apache.commons.logging.LogFactory;
24-
2524
import org.springframework.context.ApplicationContext;
2625
import org.springframework.test.context.ContextConfiguration;
2726
import org.springframework.test.context.ContextConfigurationAttributes;
@@ -86,7 +85,6 @@ public abstract class AbstractDelegatingSmartContextLoader implements SmartConte
8685
*/
8786
protected abstract SmartContextLoader getAnnotationConfigLoader();
8887

89-
9088
// --- SmartContextLoader --------------------------------------------------
9189

9290
private static String name(SmartContextLoader loader) {
@@ -112,10 +110,10 @@ private static ApplicationContext delegateLoading(SmartContextLoader loader, Mer
112110

113111
private boolean supports(SmartContextLoader loader, MergedContextConfiguration mergedConfig) {
114112
if (loader == getAnnotationConfigLoader()) {
115-
return ObjectUtils.isEmpty(mergedConfig.getLocations()) && !ObjectUtils.isEmpty(mergedConfig.getClasses());
113+
return mergedConfig.hasClasses() && !mergedConfig.hasLocations();
116114
}
117115
else {
118-
return !ObjectUtils.isEmpty(mergedConfig.getLocations()) && ObjectUtils.isEmpty(mergedConfig.getClasses());
116+
return mergedConfig.hasLocations() && !mergedConfig.hasClasses();
119117
}
120118
}
121119

@@ -152,11 +150,10 @@ private boolean supports(SmartContextLoader loader, MergedContextConfiguration m
152150
*/
153151
@Override
154152
public void processContextConfiguration(final ContextConfigurationAttributes configAttributes) {
155-
156153
Assert.notNull(configAttributes, "configAttributes must not be null");
157154
Assert.isTrue(!(configAttributes.hasLocations() && configAttributes.hasClasses()), String.format(
158-
"Cannot process locations AND classes for context configuration %s; configure one or the other, but not both.",
159-
configAttributes));
155+
"Cannot process locations AND classes for context configuration %s: "
156+
+ "configure one or the other, but not both.", configAttributes));
160157

161158
// If the original locations or classes were not empty, there's no
162159
// need to bother with default detection checks; just let the
@@ -208,15 +205,15 @@ else if (configAttributes.hasClasses()) {
208205
// throw an exception.
209206
if (!configAttributes.hasResources() && ObjectUtils.isEmpty(configAttributes.getInitializers())) {
210207
throw new IllegalStateException(String.format(
211-
"Neither %s nor %s was able to detect defaults, and no ApplicationContextInitializers " +
212-
"were declared for context configuration %s", name(getXmlLoader()),
208+
"Neither %s nor %s was able to detect defaults, and no ApplicationContextInitializers "
209+
+ "were declared for context configuration %s", name(getXmlLoader()),
213210
name(getAnnotationConfigLoader()), configAttributes));
214211
}
215212

216213
if (configAttributes.hasLocations() && configAttributes.hasClasses()) {
217214
String message = String.format(
218-
"Configuration error: both default locations AND default configuration classes " +
219-
"were detected for context configuration %s; configure one or the other, but not both.",
215+
"Configuration error: both default locations AND default configuration classes "
216+
+ "were detected for context configuration %s; configure one or the other, but not both.",
220217
configAttributes);
221218
logger.error(message);
222219
throw new IllegalStateException(message);
@@ -249,6 +246,13 @@ public ApplicationContext loadContext(MergedContextConfiguration mergedConfig) t
249246
Assert.notNull(mergedConfig, "mergedConfig must not be null");
250247
List<SmartContextLoader> candidates = Arrays.asList(getXmlLoader(), getAnnotationConfigLoader());
251248

249+
if (mergedConfig.hasLocations() && mergedConfig.hasClasses()) {
250+
throw new IllegalStateException(String.format(
251+
"Neither %s nor %s supports loading an ApplicationContext from %s: "
252+
+ "declare either 'locations' or 'classes' but not both.", name(getXmlLoader()),
253+
name(getAnnotationConfigLoader()), mergedConfig));
254+
}
255+
252256
for (SmartContextLoader loader : candidates) {
253257
// Determine if each loader can load a context from the mergedConfig. If it
254258
// can, let it; otherwise, keep iterating.
@@ -263,12 +267,12 @@ public ApplicationContext loadContext(MergedContextConfiguration mergedConfig) t
263267
return delegateLoading(getAnnotationConfigLoader(), mergedConfig);
264268
}
265269

270+
// else...
266271
throw new IllegalStateException(String.format(
267272
"Neither %s nor %s was able to load an ApplicationContext from %s.", name(getXmlLoader()),
268273
name(getAnnotationConfigLoader()), mergedConfig));
269274
}
270275

271-
272276
// --- ContextLoader -------------------------------------------------------
273277

274278
/**
@@ -279,8 +283,8 @@ public ApplicationContext loadContext(MergedContextConfiguration mergedConfig) t
279283
*/
280284
@Override
281285
public final String[] processLocations(Class<?> clazz, String... locations) {
282-
throw new UnsupportedOperationException("DelegatingSmartContextLoaders do not support the ContextLoader SPI. " +
283-
"Call processContextConfiguration(ContextConfigurationAttributes) instead.");
286+
throw new UnsupportedOperationException("DelegatingSmartContextLoaders do not support the ContextLoader SPI. "
287+
+ "Call processContextConfiguration(ContextConfigurationAttributes) instead.");
284288
}
285289

286290
/**
@@ -291,8 +295,8 @@ public final String[] processLocations(Class<?> clazz, String... locations) {
291295
*/
292296
@Override
293297
public final ApplicationContext loadContext(String... locations) throws Exception {
294-
throw new UnsupportedOperationException("DelegatingSmartContextLoaders do not support the ContextLoader SPI. " +
295-
"Call loadContext(MergedContextConfiguration) instead.");
298+
throw new UnsupportedOperationException("DelegatingSmartContextLoaders do not support the ContextLoader SPI. "
299+
+ "Call loadContext(MergedContextConfiguration) instead.");
296300
}
297301

298302
}

spring-test/src/test/java/org/springframework/test/context/support/DelegatingSmartContextLoaderTests.java

Lines changed: 37 additions & 9 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.
@@ -16,12 +16,9 @@
1616

1717
package org.springframework.test.context.support;
1818

19-
import static org.junit.Assert.assertArrayEquals;
20-
import static org.junit.Assert.assertEquals;
21-
import static org.junit.Assert.assertNotNull;
22-
import static org.junit.Assert.assertTrue;
23-
19+
import org.junit.Rule;
2420
import org.junit.Test;
21+
import org.junit.rules.ExpectedException;
2522
import org.springframework.context.ApplicationContext;
2623
import org.springframework.context.ConfigurableApplicationContext;
2724
import org.springframework.context.annotation.Bean;
@@ -31,6 +28,9 @@
3128
import org.springframework.test.context.MergedContextConfiguration;
3229
import org.springframework.util.ObjectUtils;
3330

31+
import static org.hamcrest.CoreMatchers.*;
32+
import static org.junit.Assert.*;
33+
3434
/**
3535
* Unit tests for {@link DelegatingSmartContextLoader}.
3636
*
@@ -44,15 +44,22 @@ public class DelegatingSmartContextLoaderTests {
4444

4545
private final DelegatingSmartContextLoader loader = new DelegatingSmartContextLoader();
4646

47+
@Rule
48+
public ExpectedException expectedException = ExpectedException.none();
49+
4750

4851
private static void assertEmpty(Object[] array) {
4952
assertTrue(ObjectUtils.isEmpty(array));
5053
}
5154

5255
// --- SmartContextLoader - processContextConfiguration() ------------------
5356

54-
@Test(expected = IllegalStateException.class)
57+
@Test
5558
public void processContextConfigurationWithoutLocationsAndConfigurationClassesForBogusTestClass() {
59+
expectedException.expect(IllegalStateException.class);
60+
expectedException.expectMessage(startsWith("Neither"));
61+
expectedException.expectMessage(containsString("was able to detect defaults"));
62+
5663
ContextConfigurationAttributes configAttributes = new ContextConfigurationAttributes(getClass(),
5764
EMPTY_STRING_ARRAY, EMPTY_CLASS_ARRAY, true, null, true, ContextLoader.class);
5865
loader.processContextConfiguration(configAttributes);
@@ -76,8 +83,11 @@ public void processContextConfigurationWithDefaultConfigurationClassGeneration()
7683
assertEmpty(configAttributes.getLocations());
7784
}
7885

79-
@Test(expected = IllegalStateException.class)
86+
@Test
8087
public void processContextConfigurationWithDefaultXmlConfigAndConfigurationClassGeneration() {
88+
expectedException.expect(IllegalStateException.class);
89+
expectedException.expectMessage(containsString("both default locations AND default configuration classes were detected"));
90+
8191
ContextConfigurationAttributes configAttributes = new ContextConfigurationAttributes(
8292
ImproperDuplicateDefaultXmlAndConfigClassTestCase.class, EMPTY_STRING_ARRAY, EMPTY_CLASS_ARRAY, true, null,
8393
true, ContextLoader.class);
@@ -112,13 +122,31 @@ public void loadContextWithNullConfig() throws Exception {
112122
loader.loadContext(mergedConfig);
113123
}
114124

115-
@Test(expected = IllegalStateException.class)
125+
@Test
116126
public void loadContextWithoutLocationsAndConfigurationClasses() throws Exception {
127+
expectedException.expect(IllegalStateException.class);
128+
expectedException.expectMessage(startsWith("Neither"));
129+
expectedException.expectMessage(containsString("was able to load an ApplicationContext from"));
130+
117131
MergedContextConfiguration mergedConfig = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY,
118132
EMPTY_CLASS_ARRAY, EMPTY_STRING_ARRAY, loader);
119133
loader.loadContext(mergedConfig);
120134
}
121135

136+
/**
137+
* @since 4.1
138+
*/
139+
@Test
140+
public void loadContextWithLocationsAndConfigurationClasses() throws Exception {
141+
expectedException.expect(IllegalStateException.class);
142+
expectedException.expectMessage(startsWith("Neither"));
143+
expectedException.expectMessage(endsWith("declare either 'locations' or 'classes' but not both."));
144+
145+
MergedContextConfiguration mergedConfig = new MergedContextConfiguration(getClass(),
146+
new String[] { "test.xml" }, new Class[] { getClass() }, EMPTY_STRING_ARRAY, loader);
147+
loader.loadContext(mergedConfig);
148+
}
149+
122150
private void assertApplicationContextLoadsAndContainsFooString(MergedContextConfiguration mergedConfig)
123151
throws Exception {
124152
ApplicationContext applicationContext = loader.loadContext(mergedConfig);

0 commit comments

Comments
 (0)