001/*
002 * Copyright 2002-2019 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.test.context.junit4;
018
019import java.lang.reflect.Field;
020import java.lang.reflect.Method;
021import java.util.concurrent.TimeUnit;
022
023import org.apache.commons.logging.Log;
024import org.apache.commons.logging.LogFactory;
025import org.junit.Ignore;
026import org.junit.Test;
027import org.junit.internal.runners.model.ReflectiveCallable;
028import org.junit.internal.runners.statements.ExpectException;
029import org.junit.internal.runners.statements.Fail;
030import org.junit.internal.runners.statements.FailOnTimeout;
031import org.junit.runner.Description;
032import org.junit.runner.notification.RunNotifier;
033import org.junit.runners.BlockJUnit4ClassRunner;
034import org.junit.runners.model.FrameworkMethod;
035import org.junit.runners.model.InitializationError;
036import org.junit.runners.model.Statement;
037
038import org.springframework.lang.Nullable;
039import org.springframework.test.annotation.ProfileValueUtils;
040import org.springframework.test.annotation.TestAnnotationUtils;
041import org.springframework.test.context.TestContextManager;
042import org.springframework.test.context.junit4.rules.SpringClassRule;
043import org.springframework.test.context.junit4.rules.SpringMethodRule;
044import org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks;
045import org.springframework.test.context.junit4.statements.RunAfterTestExecutionCallbacks;
046import org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks;
047import org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks;
048import org.springframework.test.context.junit4.statements.RunBeforeTestExecutionCallbacks;
049import org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks;
050import org.springframework.test.context.junit4.statements.SpringFailOnTimeout;
051import org.springframework.test.context.junit4.statements.SpringRepeat;
052import org.springframework.util.Assert;
053import org.springframework.util.ClassUtils;
054import org.springframework.util.ReflectionUtils;
055
056/**
057 * {@code SpringJUnit4ClassRunner} is a custom extension of JUnit's
058 * {@link BlockJUnit4ClassRunner} which provides functionality of the
059 * <em>Spring TestContext Framework</em> to standard JUnit tests by means of the
060 * {@link TestContextManager} and associated support classes and annotations.
061 *
062 * <p>To use this class, simply annotate a JUnit 4 based test class with
063 * {@code @RunWith(SpringJUnit4ClassRunner.class)} or {@code @RunWith(SpringRunner.class)}.
064 *
065 * <p>The following list constitutes all annotations currently supported directly
066 * or indirectly by {@code SpringJUnit4ClassRunner}. <em>(Note that additional
067 * annotations may be supported by various
068 * {@link org.springframework.test.context.TestExecutionListener TestExecutionListener}
069 * or {@link org.springframework.test.context.TestContextBootstrapper TestContextBootstrapper}
070 * implementations.)</em>
071 *
072 * <ul>
073 * <li>{@link Test#expected() @Test(expected=...)}</li>
074 * <li>{@link Test#timeout() @Test(timeout=...)}</li>
075 * <li>{@link org.springframework.test.annotation.Timed @Timed}</li>
076 * <li>{@link org.springframework.test.annotation.Repeat @Repeat}</li>
077 * <li>{@link Ignore @Ignore}</li>
078 * <li>{@link org.springframework.test.annotation.ProfileValueSourceConfiguration @ProfileValueSourceConfiguration}</li>
079 * <li>{@link org.springframework.test.annotation.IfProfileValue @IfProfileValue}</li>
080 * </ul>
081 *
082 * <p>If you would like to use the Spring TestContext Framework with a runner
083 * other than this one, use {@link SpringClassRule} and {@link SpringMethodRule}.
084 *
085 * <p><strong>NOTE:</strong> As of Spring Framework 4.3, this class requires JUnit 4.12 or higher.
086 *
087 * @author Sam Brannen
088 * @author Juergen Hoeller
089 * @since 2.5
090 * @see SpringRunner
091 * @see TestContextManager
092 * @see AbstractJUnit4SpringContextTests
093 * @see AbstractTransactionalJUnit4SpringContextTests
094 * @see org.springframework.test.context.junit4.rules.SpringClassRule
095 * @see org.springframework.test.context.junit4.rules.SpringMethodRule
096 */
097public class SpringJUnit4ClassRunner extends BlockJUnit4ClassRunner {
098
099        private static final Log logger = LogFactory.getLog(SpringJUnit4ClassRunner.class);
100
101        private static final Method withRulesMethod;
102
103        static {
104                Assert.state(ClassUtils.isPresent("org.junit.internal.Throwables", SpringJUnit4ClassRunner.class.getClassLoader()),
105                                "SpringJUnit4ClassRunner requires JUnit 4.12 or higher.");
106
107                Method method = ReflectionUtils.findMethod(SpringJUnit4ClassRunner.class, "withRules",
108                                FrameworkMethod.class, Object.class, Statement.class);
109                Assert.state(method != null, "SpringJUnit4ClassRunner requires JUnit 4.12 or higher");
110                ReflectionUtils.makeAccessible(method);
111                withRulesMethod = method;
112        }
113
114
115        private final TestContextManager testContextManager;
116
117
118        private static void ensureSpringRulesAreNotPresent(Class<?> testClass) {
119                for (Field field : testClass.getFields()) {
120                        Assert.state(!SpringClassRule.class.isAssignableFrom(field.getType()), () -> String.format(
121                                        "Detected SpringClassRule field in test class [%s], " +
122                                        "but SpringClassRule cannot be used with the SpringJUnit4ClassRunner.", testClass.getName()));
123                        Assert.state(!SpringMethodRule.class.isAssignableFrom(field.getType()), () -> String.format(
124                                        "Detected SpringMethodRule field in test class [%s], " +
125                                        "but SpringMethodRule cannot be used with the SpringJUnit4ClassRunner.", testClass.getName()));
126                }
127        }
128
129        /**
130         * Construct a new {@code SpringJUnit4ClassRunner} and initialize a
131         * {@link TestContextManager} to provide Spring testing functionality to
132         * standard JUnit tests.
133         * @param clazz the test class to be run
134         * @see #createTestContextManager(Class)
135         */
136        public SpringJUnit4ClassRunner(Class<?> clazz) throws InitializationError {
137                super(clazz);
138                if (logger.isDebugEnabled()) {
139                        logger.debug("SpringJUnit4ClassRunner constructor called with [" + clazz + "]");
140                }
141                ensureSpringRulesAreNotPresent(clazz);
142                this.testContextManager = createTestContextManager(clazz);
143        }
144
145        /**
146         * Create a new {@link TestContextManager} for the supplied test class.
147         * <p>Can be overridden by subclasses.
148         * @param clazz the test class to be managed
149         */
150        protected TestContextManager createTestContextManager(Class<?> clazz) {
151                return new TestContextManager(clazz);
152        }
153
154        /**
155         * Get the {@link TestContextManager} associated with this runner.
156         */
157        protected final TestContextManager getTestContextManager() {
158                return this.testContextManager;
159        }
160
161        /**
162         * Return a description suitable for an ignored test class if the test is
163         * disabled via {@code @IfProfileValue} at the class-level, and
164         * otherwise delegate to the parent implementation.
165         * @see ProfileValueUtils#isTestEnabledInThisEnvironment(Class)
166         */
167        @Override
168        public Description getDescription() {
169                if (!ProfileValueUtils.isTestEnabledInThisEnvironment(getTestClass().getJavaClass())) {
170                        return Description.createSuiteDescription(getTestClass().getJavaClass());
171                }
172                return super.getDescription();
173        }
174
175        /**
176         * Check whether the test is enabled in the current execution environment.
177         * <p>This prevents classes with a non-matching {@code @IfProfileValue}
178         * annotation from running altogether, even skipping the execution of
179         * {@code prepareTestInstance()} methods in {@code TestExecutionListeners}.
180         * @see ProfileValueUtils#isTestEnabledInThisEnvironment(Class)
181         * @see org.springframework.test.annotation.IfProfileValue
182         * @see org.springframework.test.context.TestExecutionListener
183         */
184        @Override
185        public void run(RunNotifier notifier) {
186                if (!ProfileValueUtils.isTestEnabledInThisEnvironment(getTestClass().getJavaClass())) {
187                        notifier.fireTestIgnored(getDescription());
188                        return;
189                }
190                super.run(notifier);
191        }
192
193        /**
194         * Wrap the {@link Statement} returned by the parent implementation with a
195         * {@code RunBeforeTestClassCallbacks} statement, thus preserving the
196         * default JUnit functionality while adding support for the Spring TestContext
197         * Framework.
198         * @see RunBeforeTestClassCallbacks
199         */
200        @Override
201        protected Statement withBeforeClasses(Statement statement) {
202                Statement junitBeforeClasses = super.withBeforeClasses(statement);
203                return new RunBeforeTestClassCallbacks(junitBeforeClasses, getTestContextManager());
204        }
205
206        /**
207         * Wrap the {@link Statement} returned by the parent implementation with a
208         * {@code RunAfterTestClassCallbacks} statement, thus preserving the default
209         * JUnit functionality while adding support for the Spring TestContext Framework.
210         * @see RunAfterTestClassCallbacks
211         */
212        @Override
213        protected Statement withAfterClasses(Statement statement) {
214                Statement junitAfterClasses = super.withAfterClasses(statement);
215                return new RunAfterTestClassCallbacks(junitAfterClasses, getTestContextManager());
216        }
217
218        /**
219         * Delegate to the parent implementation for creating the test instance and
220         * then allow the {@link #getTestContextManager() TestContextManager} to
221         * prepare the test instance before returning it.
222         * @see TestContextManager#prepareTestInstance
223         */
224        @Override
225        protected Object createTest() throws Exception {
226                Object testInstance = super.createTest();
227                getTestContextManager().prepareTestInstance(testInstance);
228                return testInstance;
229        }
230
231        /**
232         * Perform the same logic as
233         * {@link BlockJUnit4ClassRunner#runChild(FrameworkMethod, RunNotifier)},
234         * except that tests are determined to be <em>ignored</em> by
235         * {@link #isTestMethodIgnored(FrameworkMethod)}.
236         */
237        @Override
238        protected void runChild(FrameworkMethod frameworkMethod, RunNotifier notifier) {
239                Description description = describeChild(frameworkMethod);
240                if (isTestMethodIgnored(frameworkMethod)) {
241                        notifier.fireTestIgnored(description);
242                }
243                else {
244                        Statement statement;
245                        try {
246                                statement = methodBlock(frameworkMethod);
247                        }
248                        catch (Throwable ex) {
249                                statement = new Fail(ex);
250                        }
251                        runLeaf(statement, description, notifier);
252                }
253        }
254
255        /**
256         * Augment the default JUnit behavior
257         * {@linkplain #withPotentialRepeat with potential repeats} of the entire
258         * execution chain.
259         * <p>Furthermore, support for timeouts has been moved down the execution
260         * chain in order to include execution of {@link org.junit.Before @Before}
261         * and {@link org.junit.After @After} methods within the timed execution.
262         * Note that this differs from the default JUnit behavior of executing
263         * {@code @Before} and {@code @After} methods in the main thread while
264         * executing the actual test method in a separate thread. Thus, the net
265         * effect is that {@code @Before} and {@code @After} methods will be
266         * executed in the same thread as the test method. As a consequence,
267         * JUnit-specified timeouts will work fine in combination with Spring
268         * transactions. However, JUnit-specific timeouts still differ from
269         * Spring-specific timeouts in that the former execute in a separate
270         * thread while the latter simply execute in the main thread (like regular
271         * tests).
272         * @see #methodInvoker(FrameworkMethod, Object)
273         * @see #withBeforeTestExecutionCallbacks(FrameworkMethod, Object, Statement)
274         * @see #withAfterTestExecutionCallbacks(FrameworkMethod, Object, Statement)
275         * @see #possiblyExpectingExceptions(FrameworkMethod, Object, Statement)
276         * @see #withBefores(FrameworkMethod, Object, Statement)
277         * @see #withAfters(FrameworkMethod, Object, Statement)
278         * @see #withRulesReflectively(FrameworkMethod, Object, Statement)
279         * @see #withPotentialRepeat(FrameworkMethod, Object, Statement)
280         * @see #withPotentialTimeout(FrameworkMethod, Object, Statement)
281         */
282        @Override
283        protected Statement methodBlock(FrameworkMethod frameworkMethod) {
284                Object testInstance;
285                try {
286                        testInstance = new ReflectiveCallable() {
287                                @Override
288                                protected Object runReflectiveCall() throws Throwable {
289                                        return createTest();
290                                }
291                        }.run();
292                }
293                catch (Throwable ex) {
294                        return new Fail(ex);
295                }
296
297                Statement statement = methodInvoker(frameworkMethod, testInstance);
298                statement = withBeforeTestExecutionCallbacks(frameworkMethod, testInstance, statement);
299                statement = withAfterTestExecutionCallbacks(frameworkMethod, testInstance, statement);
300                statement = possiblyExpectingExceptions(frameworkMethod, testInstance, statement);
301                statement = withBefores(frameworkMethod, testInstance, statement);
302                statement = withAfters(frameworkMethod, testInstance, statement);
303                statement = withRulesReflectively(frameworkMethod, testInstance, statement);
304                statement = withPotentialRepeat(frameworkMethod, testInstance, statement);
305                statement = withPotentialTimeout(frameworkMethod, testInstance, statement);
306                return statement;
307        }
308
309        /**
310         * Invoke JUnit's private {@code withRules()} method using reflection.
311         */
312        private Statement withRulesReflectively(FrameworkMethod frameworkMethod, Object testInstance, Statement statement) {
313                Object result = ReflectionUtils.invokeMethod(withRulesMethod, this, frameworkMethod, testInstance, statement);
314                Assert.state(result instanceof Statement, "withRules mismatch");
315                return (Statement) result;
316        }
317
318        /**
319         * Return {@code true} if {@link Ignore @Ignore} is present for the supplied
320         * {@linkplain FrameworkMethod test method} or if the test method is disabled
321         * via {@code @IfProfileValue}.
322         * @see ProfileValueUtils#isTestEnabledInThisEnvironment(Method, Class)
323         */
324        protected boolean isTestMethodIgnored(FrameworkMethod frameworkMethod) {
325                Method method = frameworkMethod.getMethod();
326                return (method.isAnnotationPresent(Ignore.class) ||
327                                !ProfileValueUtils.isTestEnabledInThisEnvironment(method, getTestClass().getJavaClass()));
328        }
329
330        /**
331         * Perform the same logic as
332         * {@link BlockJUnit4ClassRunner#possiblyExpectingExceptions(FrameworkMethod, Object, Statement)}
333         * except that the <em>expected exception</em> is retrieved using
334         * {@link #getExpectedException(FrameworkMethod)}.
335         */
336        @Override
337        protected Statement possiblyExpectingExceptions(FrameworkMethod frameworkMethod, Object testInstance, Statement next) {
338                Class<? extends Throwable> expectedException = getExpectedException(frameworkMethod);
339                return (expectedException != null ? new ExpectException(next, expectedException) : next);
340        }
341
342        /**
343         * Get the {@code exception} that the supplied {@linkplain FrameworkMethod
344         * test method} is expected to throw.
345         * <p>Supports JUnit's {@link Test#expected() @Test(expected=...)} annotation.
346         * <p>Can be overridden by subclasses.
347         * @return the expected exception, or {@code null} if none was specified
348         */
349        @Nullable
350        protected Class<? extends Throwable> getExpectedException(FrameworkMethod frameworkMethod) {
351                Test test = frameworkMethod.getAnnotation(Test.class);
352                return (test != null && test.expected() != Test.None.class ? test.expected() : null);
353        }
354
355        /**
356         * Perform the same logic as
357         * {@link BlockJUnit4ClassRunner#withPotentialTimeout(FrameworkMethod, Object, Statement)}
358         * but with additional support for Spring's {@code @Timed} annotation.
359         * <p>Supports both Spring's {@link org.springframework.test.annotation.Timed @Timed}
360         * and JUnit's {@link Test#timeout() @Test(timeout=...)} annotations, but not both
361         * simultaneously.
362         * @return either a {@link SpringFailOnTimeout}, a {@link FailOnTimeout},
363         * or the supplied {@link Statement} as appropriate
364         * @see #getSpringTimeout(FrameworkMethod)
365         * @see #getJUnitTimeout(FrameworkMethod)
366         */
367        @Override
368        // Retain the following warning suppression for deprecation (even if Eclipse
369        // states it is unnecessary) since withPotentialTimeout(FrameworkMethod,Object,Statement)
370        // in BlockJUnit4ClassRunner has been deprecated.
371        @SuppressWarnings("deprecation")
372        protected Statement withPotentialTimeout(FrameworkMethod frameworkMethod, Object testInstance, Statement next) {
373                Statement statement = null;
374                long springTimeout = getSpringTimeout(frameworkMethod);
375                long junitTimeout = getJUnitTimeout(frameworkMethod);
376                if (springTimeout > 0 && junitTimeout > 0) {
377                        String msg = String.format("Test method [%s] has been configured with Spring's @Timed(millis=%s) and " +
378                                                        "JUnit's @Test(timeout=%s) annotations, but only one declaration of a 'timeout' is " +
379                                                        "permitted per test method.", frameworkMethod.getMethod(), springTimeout, junitTimeout);
380                        logger.error(msg);
381                        throw new IllegalStateException(msg);
382                }
383                else if (springTimeout > 0) {
384                        statement = new SpringFailOnTimeout(next, springTimeout);
385                }
386                else if (junitTimeout > 0) {
387                        statement = FailOnTimeout.builder().withTimeout(junitTimeout, TimeUnit.MILLISECONDS).build(next);
388                }
389                else {
390                        statement = next;
391                }
392
393                return statement;
394        }
395
396        /**
397         * Retrieve the configured JUnit {@code timeout} from the {@link Test @Test}
398         * annotation on the supplied {@linkplain FrameworkMethod test method}.
399         * @return the timeout, or {@code 0} if none was specified
400         */
401        protected long getJUnitTimeout(FrameworkMethod frameworkMethod) {
402                Test test = frameworkMethod.getAnnotation(Test.class);
403                return (test == null ? 0 : Math.max(0, test.timeout()));
404        }
405
406        /**
407         * Retrieve the configured Spring-specific {@code timeout} from the
408         * {@link org.springframework.test.annotation.Timed @Timed} annotation
409         * on the supplied {@linkplain FrameworkMethod test method}.
410         * @return the timeout, or {@code 0} if none was specified
411         * @see TestAnnotationUtils#getTimeout(Method)
412         */
413        protected long getSpringTimeout(FrameworkMethod frameworkMethod) {
414                return TestAnnotationUtils.getTimeout(frameworkMethod.getMethod());
415        }
416
417        /**
418         * Wrap the supplied {@link Statement} with a {@code RunBeforeTestExecutionCallbacks}
419         * statement, thus preserving the default functionality while adding support for the
420         * Spring TestContext Framework.
421         * @see RunBeforeTestExecutionCallbacks
422         */
423        protected Statement withBeforeTestExecutionCallbacks(FrameworkMethod frameworkMethod, Object testInstance, Statement statement) {
424                return new RunBeforeTestExecutionCallbacks(statement, testInstance, frameworkMethod.getMethod(), getTestContextManager());
425        }
426
427        /**
428         * Wrap the supplied {@link Statement} with a {@code RunAfterTestExecutionCallbacks}
429         * statement, thus preserving the default functionality while adding support for the
430         * Spring TestContext Framework.
431         * @see RunAfterTestExecutionCallbacks
432         */
433        protected Statement withAfterTestExecutionCallbacks(FrameworkMethod frameworkMethod, Object testInstance, Statement statement) {
434                return new RunAfterTestExecutionCallbacks(statement, testInstance, frameworkMethod.getMethod(), getTestContextManager());
435        }
436
437        /**
438         * Wrap the {@link Statement} returned by the parent implementation with a
439         * {@code RunBeforeTestMethodCallbacks} statement, thus preserving the
440         * default functionality while adding support for the Spring TestContext
441         * Framework.
442         * @see RunBeforeTestMethodCallbacks
443         */
444        @Override
445        protected Statement withBefores(FrameworkMethod frameworkMethod, Object testInstance, Statement statement) {
446                Statement junitBefores = super.withBefores(frameworkMethod, testInstance, statement);
447                return new RunBeforeTestMethodCallbacks(junitBefores, testInstance, frameworkMethod.getMethod(), getTestContextManager());
448        }
449
450        /**
451         * Wrap the {@link Statement} returned by the parent implementation with a
452         * {@code RunAfterTestMethodCallbacks} statement, thus preserving the
453         * default functionality while adding support for the Spring TestContext
454         * Framework.
455         * @see RunAfterTestMethodCallbacks
456         */
457        @Override
458        protected Statement withAfters(FrameworkMethod frameworkMethod, Object testInstance, Statement statement) {
459                Statement junitAfters = super.withAfters(frameworkMethod, testInstance, statement);
460                return new RunAfterTestMethodCallbacks(junitAfters, testInstance, frameworkMethod.getMethod(), getTestContextManager());
461        }
462
463        /**
464         * Wrap the supplied {@link Statement} with a {@code SpringRepeat} statement.
465         * <p>Supports Spring's {@link org.springframework.test.annotation.Repeat @Repeat}
466         * annotation.
467         * @see TestAnnotationUtils#getRepeatCount(Method)
468         * @see SpringRepeat
469         */
470        protected Statement withPotentialRepeat(FrameworkMethod frameworkMethod, Object testInstance, Statement next) {
471                return new SpringRepeat(next, frameworkMethod.getMethod());
472        }
473
474}