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}