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