001/*
002 * Copyright 2002-2018 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.rules;
018
019import java.lang.reflect.Method;
020
021import org.apache.commons.logging.Log;
022import org.apache.commons.logging.LogFactory;
023import org.junit.rules.MethodRule;
024import org.junit.runners.model.FrameworkMethod;
025import org.junit.runners.model.Statement;
026
027import org.springframework.test.context.TestContextManager;
028import org.springframework.test.context.junit4.statements.ProfileValueChecker;
029import org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks;
030import org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks;
031import org.springframework.test.context.junit4.statements.RunPrepareTestInstanceCallbacks;
032import org.springframework.test.context.junit4.statements.SpringFailOnTimeout;
033import org.springframework.test.context.junit4.statements.SpringRepeat;
034
035/**
036 * {@code SpringMethodRule} is a custom JUnit 4 {@link MethodRule} that
037 * supports instance-level and method-level features of the
038 * <em>Spring TestContext Framework</em> in standard JUnit tests by means
039 * of the {@link TestContextManager} and associated support classes and
040 * annotations.
041 *
042 * <p>In contrast to the {@link org.springframework.test.context.junit4.SpringJUnit4ClassRunner
043 * SpringJUnit4ClassRunner}, Spring's rule-based JUnit support has the advantage
044 * that it is independent of any {@link org.junit.runner.Runner Runner} and
045 * can therefore be combined with existing alternative runners like JUnit's
046 * {@code Parameterized} or third-party runners such as the {@code MockitoJUnitRunner}.
047 *
048 * <p>In order to achieve the same functionality as the {@code SpringJUnit4ClassRunner},
049 * however, a {@code SpringMethodRule} must be combined with a {@link SpringClassRule},
050 * since {@code SpringMethodRule} only supports the instance-level and method-level
051 * features of the {@code SpringJUnit4ClassRunner}.
052 *
053 * <h3>Example Usage</h3>
054 * <pre><code> public class ExampleSpringIntegrationTest {
055 *
056 *    &#064;ClassRule
057 *    public static final SpringClassRule springClassRule = new SpringClassRule();
058 *
059 *    &#064;Rule
060 *    public final SpringMethodRule springMethodRule = new SpringMethodRule();
061 *
062 *    // ...
063 * }</code></pre>
064 *
065 * <p>The following list constitutes all annotations currently supported directly
066 * or indirectly by {@code SpringMethodRule}. <em>(Note that additional annotations
067 * may be supported by various
068 * {@link org.springframework.test.context.TestExecutionListener TestExecutionListener} or
069 * {@link org.springframework.test.context.TestContextBootstrapper TestContextBootstrapper}
070 * implementations.)</em>
071 *
072 * <ul>
073 * <li>{@link org.springframework.test.annotation.Timed @Timed}</li>
074 * <li>{@link org.springframework.test.annotation.Repeat @Repeat}</li>
075 * <li>{@link org.springframework.test.annotation.ProfileValueSourceConfiguration @ProfileValueSourceConfiguration}</li>
076 * <li>{@link org.springframework.test.annotation.IfProfileValue @IfProfileValue}</li>
077 * </ul>
078 *
079 * <p><strong>NOTE:</strong> As of Spring Framework 4.3, this class requires JUnit 4.12 or higher.
080 *
081 * <p><strong>WARNING:</strong> Due to the shortcomings of JUnit rules, the
082 * {@code SpringMethodRule} does <strong>not</strong> support the
083 * {@code beforeTestExecution()} and {@code afterTestExecution()} callbacks of the
084 * {@link org.springframework.test.context.TestExecutionListener TestExecutionListener}
085 * API.
086 *
087 * @author Sam Brannen
088 * @author Philippe Marschall
089 * @since 4.2
090 * @see #apply(Statement, FrameworkMethod, Object)
091 * @see SpringClassRule
092 * @see org.springframework.test.context.TestContextManager
093 * @see org.springframework.test.context.junit4.SpringJUnit4ClassRunner
094 */
095public class SpringMethodRule implements MethodRule {
096
097        private static final Log logger = LogFactory.getLog(SpringMethodRule.class);
098
099
100        /**
101         * Apply <em>instance-level</em> and <em>method-level</em> features of
102         * the <em>Spring TestContext Framework</em> to the supplied {@code base}
103         * statement.
104         * <p>Specifically, this method invokes the
105         * {@link TestContextManager#prepareTestInstance prepareTestInstance()},
106         * {@link TestContextManager#beforeTestMethod beforeTestMethod()}, and
107         * {@link TestContextManager#afterTestMethod afterTestMethod()} methods
108         * on the {@code TestContextManager}, potentially with Spring timeouts
109         * and repetitions.
110         * <p>In addition, this method checks whether the test is enabled in
111         * the current execution environment. This prevents methods with a
112         * non-matching {@code @IfProfileValue} annotation from running altogether,
113         * even skipping the execution of {@code prepareTestInstance()} methods
114         * in {@code TestExecutionListeners}.
115         * @param base the base {@code Statement} that this rule should be applied to
116         * @param frameworkMethod the method which is about to be invoked on the test instance
117         * @param testInstance the current test instance
118         * @return a statement that wraps the supplied {@code base} with instance-level
119         * and method-level features of the Spring TestContext Framework
120         * @see #withBeforeTestMethodCallbacks
121         * @see #withAfterTestMethodCallbacks
122         * @see #withPotentialRepeat
123         * @see #withPotentialTimeout
124         * @see #withTestInstancePreparation
125         * @see #withProfileValueCheck
126         */
127        @Override
128        public Statement apply(Statement base, FrameworkMethod frameworkMethod, Object testInstance) {
129                Method testMethod = frameworkMethod.getMethod();
130                if (logger.isDebugEnabled()) {
131                        logger.debug("Applying SpringMethodRule to test method [" + testMethod + "]");
132                }
133                Class<?> testClass = testInstance.getClass();
134                TestContextManager testContextManager = SpringClassRule.getTestContextManager(testClass);
135
136                Statement statement = base;
137                statement = withBeforeTestMethodCallbacks(statement, testMethod, testInstance, testContextManager);
138                statement = withAfterTestMethodCallbacks(statement, testMethod, testInstance, testContextManager);
139                statement = withTestInstancePreparation(statement, testInstance, testContextManager);
140                statement = withPotentialRepeat(statement, testMethod, testInstance);
141                statement = withPotentialTimeout(statement, testMethod, testInstance);
142                statement = withProfileValueCheck(statement, testMethod, testInstance);
143                return statement;
144        }
145
146        /**
147         * Wrap the supplied {@link Statement} with a {@code RunBeforeTestMethodCallbacks} statement.
148         * @see RunBeforeTestMethodCallbacks
149         */
150        private Statement withBeforeTestMethodCallbacks(Statement next, Method testMethod,
151                        Object testInstance, TestContextManager testContextManager) {
152
153                return new RunBeforeTestMethodCallbacks(
154                                next, testInstance, testMethod, testContextManager);
155        }
156
157        /**
158         * Wrap the supplied {@link Statement} with a {@code RunAfterTestMethodCallbacks} statement.
159         * @see RunAfterTestMethodCallbacks
160         */
161        private Statement withAfterTestMethodCallbacks(Statement next, Method testMethod,
162                        Object testInstance, TestContextManager testContextManager) {
163
164                return new RunAfterTestMethodCallbacks(
165                                next, testInstance, testMethod, testContextManager);
166        }
167
168        /**
169         * Wrap the supplied {@link Statement} with a {@code RunPrepareTestInstanceCallbacks} statement.
170         * @see RunPrepareTestInstanceCallbacks
171         */
172        private Statement withTestInstancePreparation(
173                        Statement next, Object testInstance, TestContextManager testContextManager) {
174
175                return new RunPrepareTestInstanceCallbacks(next, testInstance, testContextManager);
176        }
177
178        /**
179         * Wrap the supplied {@link Statement} with a {@code SpringRepeat} statement.
180         * <p>Supports Spring's {@link org.springframework.test.annotation.Repeat @Repeat}
181         * annotation.
182         * @see SpringRepeat
183         */
184        private Statement withPotentialRepeat(Statement next, Method testMethod, Object testInstance) {
185                return new SpringRepeat(next, testMethod);
186        }
187
188        /**
189         * Wrap the supplied {@link Statement} with a {@code SpringFailOnTimeout} statement.
190         * <p>Supports Spring's {@link org.springframework.test.annotation.Timed @Timed}
191         * annotation.
192         * @see SpringFailOnTimeout
193         */
194        private Statement withPotentialTimeout(Statement next, Method testMethod, Object testInstance) {
195                return new SpringFailOnTimeout(next, testMethod);
196        }
197
198        /**
199         * Wrap the supplied {@link Statement} with a {@code ProfileValueChecker} statement.
200         * @see ProfileValueChecker
201         */
202        private Statement withProfileValueCheck(Statement next, Method testMethod, Object testInstance) {
203                return new ProfileValueChecker(next, testInstance.getClass(), testMethod);
204        }
205
206}