Skip to content

Commit d1b1c4f

Browse files
committed
Introduce JUnit Rule alternative to SpringJUnit4ClassRunner
Since Spring Framework 2.5, support for integrating the Spring TestContext Framework (TCF) into JUnit 4 based tests has been provided via the SpringJUnit4ClassRunner, but this approach precludes the ability for tests to be run with alternative runners like JUnit's Parameterized or third-party runners such as the MockitoJUnitRunner. This commit remedies this situation by introducing @ClassRule and @rule based alternatives to the SpringJUnit4ClassRunner. These rules are independent of any Runner and can therefore be combined with alternative runners. Due to the limitations of JUnit's implementation of rules, as of JUnit 4.12 it is currently impossible to create a single rule that can be applied both at the class level and at the method level (with access to the test instance). Consequently, this commit introduces the following two rules that must be used together. - SpringClassRule: a JUnit TestRule that provides the class-level functionality of the TCF to JUnit-based tests - SpringMethodRule: a JUnit MethodRule that provides the instance-level and method-level functionality of the TCF to JUnit-based tests In addition, this commit also introduces the following new JUnit Statements for use with rules: - RunPrepareTestInstanceCallbacks - ProfileValueChecker Issue: SPR-7731
1 parent 49fff75 commit d1b1c4f

File tree

40 files changed

+1970
-168
lines changed

40 files changed

+1970
-168
lines changed

spring-test/src/main/java/org/springframework/test/context/junit4/AbstractJUnit4SpringContextTests.java

+18-11
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2014 the original author or authors.
2+
* Copyright 2002-2015 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.
@@ -18,6 +18,7 @@
1818

1919
import org.apache.commons.logging.Log;
2020
import org.apache.commons.logging.LogFactory;
21+
2122
import org.junit.runner.RunWith;
2223

2324
import org.springframework.context.ApplicationContext;
@@ -36,12 +37,12 @@
3637
* in a <strong>JUnit</strong> environment.
3738
*
3839
* <p>Concrete subclasses should typically declare a class-level
39-
* {@link ContextConfiguration &#064;ContextConfiguration} annotation to
40-
* configure the {@link ApplicationContext application context} {@link
40+
* {@link ContextConfiguration @ContextConfiguration} annotation to
41+
* configure the {@linkplain ApplicationContext application context} {@link
4142
* ContextConfiguration#locations() resource locations} or {@link
4243
* ContextConfiguration#classes() annotated classes}. <em>If your test does not
43-
* need to load an application context, you may choose to omit the {@link
44-
* ContextConfiguration &#064;ContextConfiguration} declaration and to configure
44+
* need to load an application context, you may choose to omit the
45+
* {@link ContextConfiguration @ContextConfiguration} declaration and to configure
4546
* the appropriate {@link org.springframework.test.context.TestExecutionListener
4647
* TestExecutionListeners} manually.</em>
4748
*
@@ -54,12 +55,18 @@
5455
* <li>{@link org.springframework.test.context.support.DirtiesContextTestExecutionListener}
5556
* </ul>
5657
*
57-
* <p>Note: this class serves only as a convenience for extension. If you do not
58-
* wish for your test classes to be tied to a Spring-specific class hierarchy,
59-
* you may configure your own custom test classes by using
60-
* {@link SpringJUnit4ClassRunner}, {@link ContextConfiguration
61-
* &#064;ContextConfiguration}, {@link TestExecutionListeners
62-
* &#064;TestExecutionListeners}, etc.
58+
* <p>This class serves only as a convenience for extension.
59+
* <ul>
60+
* <li>If you do not wish for your test classes to be tied to a Spring-specific
61+
* class hierarchy, you may configure your own custom test classes by using
62+
* {@link SpringJUnit4ClassRunner}, {@link ContextConfiguration @ContextConfiguration},
63+
* {@link TestExecutionListeners @TestExecutionListeners}, etc.</li>
64+
* <li>If you wish to extend this class and use a runner other than the
65+
* {@link SpringJUnit4ClassRunner}, as of Spring Framework 4.2 you can use
66+
* {@link org.springframework.test.context.junit4.rules.SpringClassRule SpringClassRule} and
67+
* {@link org.springframework.test.context.junit4.rules.SpringMethodRule SpringMethodRule}
68+
* and specify your runner of choice via {@link RunWith @RunWith(...)}.</li>
69+
* </ul>
6370
*
6471
* <p><strong>NOTE:</strong> As of Spring Framework 4.1, this class requires JUnit 4.9 or higher.
6572
*

spring-test/src/main/java/org/springframework/test/context/junit4/AbstractTransactionalJUnit4SpringContextTests.java

+13-8
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2014 the original author or authors.
2+
* Copyright 2002-2015 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.
@@ -60,13 +60,18 @@
6060
* <li>{@link org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener}
6161
* </ul>
6262
*
63-
* <p>Note: this class serves only as a convenience for extension. If you do not
64-
* wish for your test classes to be tied to a Spring-specific class hierarchy,
65-
* you may configure your own custom test classes by using
66-
* {@link SpringJUnit4ClassRunner}, {@link ContextConfiguration
67-
* &#064;ContextConfiguration}, {@link TestExecutionListeners
68-
* &#064;TestExecutionListeners}, {@link Transactional &#064;Transactional},
69-
* etc.
63+
* <p>This class serves only as a convenience for extension.
64+
* <ul>
65+
* <li>If you do not wish for your test classes to be tied to a Spring-specific
66+
* class hierarchy, you may configure your own custom test classes by using
67+
* {@link SpringJUnit4ClassRunner}, {@link ContextConfiguration @ContextConfiguration},
68+
* {@link TestExecutionListeners @TestExecutionListeners}, etc.</li>
69+
* <li>If you wish to extend this class and use a runner other than the
70+
* {@link SpringJUnit4ClassRunner}, as of Spring Framework 4.2 you can use
71+
* {@link org.springframework.test.context.junit4.rules.SpringClassRule SpringClassRule} and
72+
* {@link org.springframework.test.context.junit4.rules.SpringMethodRule SpringMethodRule}
73+
* and specify your runner of choice via {@link org.junit.runner.RunWith @RunWith(...)}.</li>
74+
* </ul>
7075
*
7176
* <p><strong>NOTE:</strong> As of Spring Framework 4.1, this class requires JUnit 4.9 or higher.
7277
*

spring-test/src/main/java/org/springframework/test/context/junit4/SpringJUnit4ClassRunner.java

+28-6
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

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

19+
import java.lang.reflect.Field;
1920
import java.lang.reflect.Method;
2021

2122
import org.apache.commons.logging.Log;
@@ -41,6 +42,8 @@
4142
import org.springframework.test.annotation.Repeat;
4243
import org.springframework.test.annotation.Timed;
4344
import org.springframework.test.context.TestContextManager;
45+
import org.springframework.test.context.junit4.rules.SpringClassRule;
46+
import org.springframework.test.context.junit4.rules.SpringMethodRule;
4447
import org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks;
4548
import org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks;
4649
import org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks;
@@ -72,6 +75,9 @@
7275
* <li>{@link org.springframework.test.annotation.IfProfileValue @IfProfileValue}</li>
7376
* </ul>
7477
*
78+
* <p>If you would like to use the Spring TestContext Framework with a runner
79+
* other than this one, use {@link SpringClassRule} and {@link SpringMethodRule}.
80+
*
7581
* <p><strong>NOTE:</strong> As of Spring Framework 4.1, this class requires JUnit 4.9 or higher.
7682
*
7783
* @author Sam Brannen
@@ -80,6 +86,8 @@
8086
* @see TestContextManager
8187
* @see AbstractJUnit4SpringContextTests
8288
* @see AbstractTransactionalJUnit4SpringContextTests
89+
* @see org.springframework.test.context.junit4.rules.SpringClassRule
90+
* @see org.springframework.test.context.junit4.rules.SpringMethodRule
8391
*/
8492
@SuppressWarnings("deprecation")
8593
public class SpringJUnit4ClassRunner extends BlockJUnit4ClassRunner {
@@ -101,6 +109,19 @@ public class SpringJUnit4ClassRunner extends BlockJUnit4ClassRunner {
101109
private final TestContextManager testContextManager;
102110

103111

112+
private static void ensureSpringRulesAreNotPresent(Class<?> testClass) {
113+
for (Field field : testClass.getFields()) {
114+
if (SpringClassRule.class.isAssignableFrom(field.getType())) {
115+
throw new IllegalStateException(String.format("Detected SpringClassRule field in test class [%s], but "
116+
+ "SpringClassRule cannot be used with the SpringJUnit4ClassRunner.", testClass.getName()));
117+
}
118+
if (SpringMethodRule.class.isAssignableFrom(field.getType())) {
119+
throw new IllegalStateException(String.format("Detected SpringMethodRule field in test class [%s], "
120+
+ "but SpringMethodRule cannot be used with the SpringJUnit4ClassRunner.", testClass.getName()));
121+
}
122+
}
123+
}
124+
104125
/**
105126
* Construct a new {@code SpringJUnit4ClassRunner} and initialize a
106127
* {@link TestContextManager} to provide Spring testing functionality to
@@ -113,6 +134,7 @@ public SpringJUnit4ClassRunner(Class<?> clazz) throws InitializationError {
113134
if (logger.isDebugEnabled()) {
114135
logger.debug("SpringJUnit4ClassRunner constructor called with [" + clazz + "].");
115136
}
137+
ensureSpringRulesAreNotPresent(clazz);
116138
this.testContextManager = createTestContextManager(clazz);
117139
}
118140

@@ -166,7 +188,7 @@ public void run(RunNotifier notifier) {
166188

167189
/**
168190
* Wrap the {@link Statement} returned by the parent implementation with a
169-
* {@link RunBeforeTestClassCallbacks} statement, thus preserving the
191+
* {@code RunBeforeTestClassCallbacks} statement, thus preserving the
170192
* default JUnit functionality while adding support for the Spring TestContext
171193
* Framework.
172194
* @see RunBeforeTestClassCallbacks
@@ -179,7 +201,7 @@ protected Statement withBeforeClasses(Statement statement) {
179201

180202
/**
181203
* Wrap the {@link Statement} returned by the parent implementation with a
182-
* {@link RunAfterTestClassCallbacks} statement, thus preserving the default
204+
* {@code RunAfterTestClassCallbacks} statement, thus preserving the default
183205
* JUnit functionality while adding support for the Spring TestContext Framework.
184206
* @see RunAfterTestClassCallbacks
185207
*/
@@ -393,7 +415,7 @@ protected long getSpringTimeout(FrameworkMethod frameworkMethod) {
393415

394416
/**
395417
* Wrap the {@link Statement} returned by the parent implementation with a
396-
* {@link RunBeforeTestMethodCallbacks} statement, thus preserving the
418+
* {@code RunBeforeTestMethodCallbacks} statement, thus preserving the
397419
* default functionality while adding support for the Spring TestContext
398420
* Framework.
399421
* @see RunBeforeTestMethodCallbacks
@@ -407,7 +429,7 @@ protected Statement withBefores(FrameworkMethod frameworkMethod, Object testInst
407429

408430
/**
409431
* Wrap the {@link Statement} returned by the parent implementation with a
410-
* {@link RunAfterTestMethodCallbacks} statement, thus preserving the
432+
* {@code RunAfterTestMethodCallbacks} statement, thus preserving the
411433
* default functionality while adding support for the Spring TestContext
412434
* Framework.
413435
* @see RunAfterTestMethodCallbacks
@@ -423,10 +445,10 @@ protected Statement withAfters(FrameworkMethod frameworkMethod, Object testInsta
423445
* Return a {@link Statement} that potentially repeats the execution of
424446
* the {@code next} statement.
425447
* <p>Supports Spring's {@link Repeat @Repeat} annotation by returning a
426-
* {@link SpringRepeat} statement initialized with the configured repeat
448+
* {@code SpringRepeat} statement initialized with the configured repeat
427449
* count (if greater than {@code 1}); otherwise, the supplied statement
428450
* is returned unmodified.
429-
* @return either a {@link SpringRepeat} or the supplied {@link Statement}
451+
* @return either a {@code SpringRepeat} or the supplied {@code Statement}
430452
* as appropriate
431453
* @see SpringRepeat
432454
*/

0 commit comments

Comments
 (0)