001/* 002 * Copyright 2002-2015 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.Field; 020import java.lang.reflect.Modifier; 021import java.util.Map; 022import java.util.concurrent.ConcurrentHashMap; 023 024import org.apache.commons.logging.Log; 025import org.apache.commons.logging.LogFactory; 026 027import org.junit.Rule; 028import org.junit.rules.TestRule; 029import org.junit.runner.Description; 030import org.junit.runners.model.Statement; 031 032import org.springframework.test.context.TestContextManager; 033import org.springframework.test.context.junit4.statements.ProfileValueChecker; 034import org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks; 035import org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks; 036import org.springframework.util.Assert; 037import org.springframework.util.ClassUtils; 038 039/** 040 * {@code SpringClassRule} is a custom JUnit {@link TestRule} that supports 041 * <em>class-level</em> features of the <em>Spring TestContext Framework</em> 042 * in standard JUnit tests by means of the {@link TestContextManager} and 043 * associated support classes and annotations. 044 * 045 * <p>In contrast to the {@link org.springframework.test.context.junit4.SpringJUnit4ClassRunner 046 * SpringJUnit4ClassRunner}, Spring's rule-based JUnit support has the advantage 047 * that it is independent of any {@link org.junit.runner.Runner Runner} and 048 * can therefore be combined with existing alternative runners like JUnit's 049 * {@code Parameterized} or third-party runners such as the {@code MockitoJUnitRunner}. 050 * 051 * <p>In order to achieve the same functionality as the {@code SpringJUnit4ClassRunner}, 052 * however, a {@code SpringClassRule} must be combined with a {@link SpringMethodRule}, 053 * since {@code SpringClassRule} only supports the class-level features of the 054 * {@code SpringJUnit4ClassRunner}. 055 * 056 * <h3>Example Usage</h3> 057 * <pre><code> public class ExampleSpringIntegrationTest { 058 * 059 * @ClassRule 060 * public static final SpringClassRule SPRING_CLASS_RULE = new SpringClassRule(); 061 * 062 * @Rule 063 * public final SpringMethodRule springMethodRule = new SpringMethodRule(); 064 * 065 * // ... 066 * }</code></pre> 067 * 068 * <p>The following list constitutes all annotations currently supported directly 069 * or indirectly by {@code SpringClassRule}. <em>(Note that additional annotations 070 * may be supported by various 071 * {@link org.springframework.test.context.TestExecutionListener TestExecutionListener} or 072 * {@link org.springframework.test.context.TestContextBootstrapper TestContextBootstrapper} 073 * implementations.)</em> 074 * 075 * <ul> 076 * <li>{@link org.springframework.test.annotation.ProfileValueSourceConfiguration @ProfileValueSourceConfiguration}</li> 077 * <li>{@link org.springframework.test.annotation.IfProfileValue @IfProfileValue}</li> 078 * </ul> 079 * 080 * <p><strong>NOTE:</strong> As of Spring Framework 4.3, this class requires JUnit 4.12 or higher. 081 * 082 * @author Sam Brannen 083 * @author Philippe Marschall 084 * @since 4.2 085 * @see #apply(Statement, Description) 086 * @see SpringMethodRule 087 * @see org.springframework.test.context.TestContextManager 088 * @see org.springframework.test.context.junit4.SpringJUnit4ClassRunner 089 */ 090public class SpringClassRule implements TestRule { 091 092 private static final Log logger = LogFactory.getLog(SpringClassRule.class); 093 094 /** 095 * Cache of {@code TestContextManagers} keyed by test class. 096 */ 097 private static final Map<Class<?>, TestContextManager> testContextManagerCache = 098 new ConcurrentHashMap<Class<?>, TestContextManager>(64); 099 100 static { 101 if (!ClassUtils.isPresent("org.junit.internal.Throwables", SpringClassRule.class.getClassLoader())) { 102 throw new IllegalStateException("SpringClassRule requires JUnit 4.12 or higher."); 103 } 104 } 105 106 107 /** 108 * Apply <em>class-level</em> features of the <em>Spring TestContext 109 * Framework</em> to the supplied {@code base} statement. 110 * <p>Specifically, this method retrieves the {@link TestContextManager} 111 * used by this rule and its associated {@link SpringMethodRule} and 112 * invokes the {@link TestContextManager#beforeTestClass() beforeTestClass()} 113 * and {@link TestContextManager#afterTestClass() afterTestClass()} methods 114 * on the {@code TestContextManager}. 115 * <p>In addition, this method checks whether the test is enabled in 116 * the current execution environment. This prevents classes with a 117 * non-matching {@code @IfProfileValue} annotation from running altogether, 118 * even skipping the execution of {@code beforeTestClass()} methods 119 * in {@code TestExecutionListeners}. 120 * @param base the base {@code Statement} that this rule should be applied to 121 * @param description a {@code Description} of the current test execution 122 * @return a statement that wraps the supplied {@code base} with class-level 123 * features of the Spring TestContext Framework 124 * @see #getTestContextManager 125 * @see #withBeforeTestClassCallbacks 126 * @see #withAfterTestClassCallbacks 127 * @see #withProfileValueCheck 128 * @see #withTestContextManagerCacheEviction 129 */ 130 @Override 131 public Statement apply(Statement base, Description description) { 132 Class<?> testClass = description.getTestClass(); 133 if (logger.isDebugEnabled()) { 134 logger.debug("Applying SpringClassRule to test class [" + testClass.getName() + "]"); 135 } 136 validateSpringMethodRuleConfiguration(testClass); 137 TestContextManager testContextManager = getTestContextManager(testClass); 138 139 Statement statement = base; 140 statement = withBeforeTestClassCallbacks(statement, testContextManager); 141 statement = withAfterTestClassCallbacks(statement, testContextManager); 142 statement = withProfileValueCheck(statement, testClass); 143 statement = withTestContextManagerCacheEviction(statement, testClass); 144 return statement; 145 } 146 147 /** 148 * Wrap the supplied {@code statement} with a {@code RunBeforeTestClassCallbacks} statement. 149 * @see RunBeforeTestClassCallbacks 150 */ 151 private Statement withBeforeTestClassCallbacks(Statement statement, TestContextManager testContextManager) { 152 return new RunBeforeTestClassCallbacks(statement, testContextManager); 153 } 154 155 /** 156 * Wrap the supplied {@code statement} with a {@code RunAfterTestClassCallbacks} statement. 157 * @see RunAfterTestClassCallbacks 158 */ 159 private Statement withAfterTestClassCallbacks(Statement statement, TestContextManager testContextManager) { 160 return new RunAfterTestClassCallbacks(statement, testContextManager); 161 } 162 163 /** 164 * Wrap the supplied {@code statement} with a {@code ProfileValueChecker} statement. 165 * @see ProfileValueChecker 166 */ 167 private Statement withProfileValueCheck(Statement statement, Class<?> testClass) { 168 return new ProfileValueChecker(statement, testClass, null); 169 } 170 171 /** 172 * Wrap the supplied {@code statement} with a {@code TestContextManagerCacheEvictor} statement. 173 * @see TestContextManagerCacheEvictor 174 */ 175 private Statement withTestContextManagerCacheEviction(Statement statement, Class<?> testClass) { 176 return new TestContextManagerCacheEvictor(statement, testClass); 177 } 178 179 180 /** 181 * Throw an {@link IllegalStateException} if the supplied {@code testClass} 182 * does not declare a {@code public SpringMethodRule} field that is 183 * annotated with {@code @Rule}. 184 */ 185 private static void validateSpringMethodRuleConfiguration(Class<?> testClass) { 186 Field ruleField = null; 187 188 for (Field field : testClass.getFields()) { 189 int modifiers = field.getModifiers(); 190 if (!Modifier.isStatic(modifiers) && Modifier.isPublic(modifiers) && 191 SpringMethodRule.class.isAssignableFrom(field.getType())) { 192 ruleField = field; 193 break; 194 } 195 } 196 197 if (ruleField == null) { 198 throw new IllegalStateException(String.format( 199 "Failed to find 'public SpringMethodRule' field in test class [%s]. " + 200 "Consult the javadoc for SpringClassRule for details.", testClass.getName())); 201 } 202 203 if (!ruleField.isAnnotationPresent(Rule.class)) { 204 throw new IllegalStateException(String.format( 205 "SpringMethodRule field [%s] must be annotated with JUnit's @Rule annotation. " + 206 "Consult the javadoc for SpringClassRule for details.", ruleField)); 207 } 208 } 209 210 /** 211 * Get the {@link TestContextManager} associated with the supplied test class. 212 * @param testClass the test class to be managed; never {@code null} 213 */ 214 static TestContextManager getTestContextManager(Class<?> testClass) { 215 Assert.notNull(testClass, "testClass must not be null"); 216 synchronized (testContextManagerCache) { 217 TestContextManager testContextManager = testContextManagerCache.get(testClass); 218 if (testContextManager == null) { 219 testContextManager = new TestContextManager(testClass); 220 testContextManagerCache.put(testClass, testContextManager); 221 } 222 return testContextManager; 223 } 224 } 225 226 227 private static class TestContextManagerCacheEvictor extends Statement { 228 229 private final Statement next; 230 231 private final Class<?> testClass; 232 233 234 TestContextManagerCacheEvictor(Statement next, Class<?> testClass) { 235 this.next = next; 236 this.testClass = testClass; 237 } 238 239 @Override 240 public void evaluate() throws Throwable { 241 try { 242 next.evaluate(); 243 } 244 finally { 245 testContextManagerCache.remove(testClass); 246 } 247 } 248 } 249 250}