Skip to content

Commit

Permalink
Introduce JUnit Rule alternative to SpringJUnit4ClassRunner
Browse files Browse the repository at this point in the history
Since Spring Framework 2.5, support for integrating the Spring
TestContext Framework (TCF) into JUnit 4 based tests has been provided
via the SpringJUnit4ClassRunner, but this approach precludes the
ability for tests to be run with alternative runners like JUnit's
Parameterized or third-party runners such as the MockitoJUnitRunner.

This commit remedies this situation by introducing @ClassRule and @rule
based alternatives to the SpringJUnit4ClassRunner. These rules are
independent of any Runner and can therefore be combined with
alternative runners.

Due to the limitations of JUnit's implementation of rules, as of JUnit
4.12 it is currently impossible to create a single rule that can be
applied both at the class level and at the method level (with access to
the test instance). Consequently, this commit introduces the following
two rules that must be used together.

 - SpringClassRule: a JUnit TestRule that provides the class-level
   functionality of the TCF to JUnit-based tests

 - SpringMethodRule: a JUnit MethodRule that provides the
   instance-level and method-level functionality of the TCF to
   JUnit-based tests

In addition, this commit also introduces the following new JUnit
Statements for use with rules:

 - RunPrepareTestInstanceCallbacks

 - ProfileValueChecker

Issue: SPR-7731
  • Loading branch information
sbrannen committed May 16, 2015
1 parent 49fff75 commit d1b1c4f
Show file tree
Hide file tree
Showing 40 changed files with 1,970 additions and 168 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2014 the original author or authors.
* Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -18,6 +18,7 @@

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.junit.runner.RunWith;

import org.springframework.context.ApplicationContext;
Expand All @@ -36,12 +37,12 @@
* in a <strong>JUnit</strong> environment.
*
* <p>Concrete subclasses should typically declare a class-level
* {@link ContextConfiguration &#064;ContextConfiguration} annotation to
* configure the {@link ApplicationContext application context} {@link
* {@link ContextConfiguration @ContextConfiguration} annotation to
* configure the {@linkplain ApplicationContext application context} {@link
* ContextConfiguration#locations() resource locations} or {@link
* ContextConfiguration#classes() annotated classes}. <em>If your test does not
* need to load an application context, you may choose to omit the {@link
* ContextConfiguration &#064;ContextConfiguration} declaration and to configure
* need to load an application context, you may choose to omit the
* {@link ContextConfiguration @ContextConfiguration} declaration and to configure
* the appropriate {@link org.springframework.test.context.TestExecutionListener
* TestExecutionListeners} manually.</em>
*
Expand All @@ -54,12 +55,18 @@
* <li>{@link org.springframework.test.context.support.DirtiesContextTestExecutionListener}
* </ul>
*
* <p>Note: this class serves only as a convenience for extension. If you do not
* wish for your test classes to be tied to a Spring-specific class hierarchy,
* you may configure your own custom test classes by using
* {@link SpringJUnit4ClassRunner}, {@link ContextConfiguration
* &#064;ContextConfiguration}, {@link TestExecutionListeners
* &#064;TestExecutionListeners}, etc.
* <p>This class serves only as a convenience for extension.
* <ul>
* <li>If you do not wish for your test classes to be tied to a Spring-specific
* class hierarchy, you may configure your own custom test classes by using
* {@link SpringJUnit4ClassRunner}, {@link ContextConfiguration @ContextConfiguration},
* {@link TestExecutionListeners @TestExecutionListeners}, etc.</li>
* <li>If you wish to extend this class and use a runner other than the
* {@link SpringJUnit4ClassRunner}, as of Spring Framework 4.2 you can use
* {@link org.springframework.test.context.junit4.rules.SpringClassRule SpringClassRule} and
* {@link org.springframework.test.context.junit4.rules.SpringMethodRule SpringMethodRule}
* and specify your runner of choice via {@link RunWith @RunWith(...)}.</li>
* </ul>
*
* <p><strong>NOTE:</strong> As of Spring Framework 4.1, this class requires JUnit 4.9 or higher.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2014 the original author or authors.
* Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -60,13 +60,18 @@
* <li>{@link org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener}
* </ul>
*
* <p>Note: this class serves only as a convenience for extension. If you do not
* wish for your test classes to be tied to a Spring-specific class hierarchy,
* you may configure your own custom test classes by using
* {@link SpringJUnit4ClassRunner}, {@link ContextConfiguration
* &#064;ContextConfiguration}, {@link TestExecutionListeners
* &#064;TestExecutionListeners}, {@link Transactional &#064;Transactional},
* etc.
* <p>This class serves only as a convenience for extension.
* <ul>
* <li>If you do not wish for your test classes to be tied to a Spring-specific
* class hierarchy, you may configure your own custom test classes by using
* {@link SpringJUnit4ClassRunner}, {@link ContextConfiguration @ContextConfiguration},
* {@link TestExecutionListeners @TestExecutionListeners}, etc.</li>
* <li>If you wish to extend this class and use a runner other than the
* {@link SpringJUnit4ClassRunner}, as of Spring Framework 4.2 you can use
* {@link org.springframework.test.context.junit4.rules.SpringClassRule SpringClassRule} and
* {@link org.springframework.test.context.junit4.rules.SpringMethodRule SpringMethodRule}
* and specify your runner of choice via {@link org.junit.runner.RunWith @RunWith(...)}.</li>
* </ul>
*
* <p><strong>NOTE:</strong> As of Spring Framework 4.1, this class requires JUnit 4.9 or higher.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

package org.springframework.test.context.junit4;

import java.lang.reflect.Field;
import java.lang.reflect.Method;

import org.apache.commons.logging.Log;
Expand All @@ -41,6 +42,8 @@
import org.springframework.test.annotation.Repeat;
import org.springframework.test.annotation.Timed;
import org.springframework.test.context.TestContextManager;
import org.springframework.test.context.junit4.rules.SpringClassRule;
import org.springframework.test.context.junit4.rules.SpringMethodRule;
import org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks;
import org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks;
import org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks;
Expand Down Expand Up @@ -72,6 +75,9 @@
* <li>{@link org.springframework.test.annotation.IfProfileValue @IfProfileValue}</li>
* </ul>
*
* <p>If you would like to use the Spring TestContext Framework with a runner
* other than this one, use {@link SpringClassRule} and {@link SpringMethodRule}.
*
* <p><strong>NOTE:</strong> As of Spring Framework 4.1, this class requires JUnit 4.9 or higher.
*
* @author Sam Brannen
Expand All @@ -80,6 +86,8 @@
* @see TestContextManager
* @see AbstractJUnit4SpringContextTests
* @see AbstractTransactionalJUnit4SpringContextTests
* @see org.springframework.test.context.junit4.rules.SpringClassRule
* @see org.springframework.test.context.junit4.rules.SpringMethodRule
*/
@SuppressWarnings("deprecation")
public class SpringJUnit4ClassRunner extends BlockJUnit4ClassRunner {
Expand All @@ -101,6 +109,19 @@ public class SpringJUnit4ClassRunner extends BlockJUnit4ClassRunner {
private final TestContextManager testContextManager;


private static void ensureSpringRulesAreNotPresent(Class<?> testClass) {
for (Field field : testClass.getFields()) {
if (SpringClassRule.class.isAssignableFrom(field.getType())) {
throw new IllegalStateException(String.format("Detected SpringClassRule field in test class [%s], but "
+ "SpringClassRule cannot be used with the SpringJUnit4ClassRunner.", testClass.getName()));
}
if (SpringMethodRule.class.isAssignableFrom(field.getType())) {
throw new IllegalStateException(String.format("Detected SpringMethodRule field in test class [%s], "
+ "but SpringMethodRule cannot be used with the SpringJUnit4ClassRunner.", testClass.getName()));
}
}
}

/**
* Construct a new {@code SpringJUnit4ClassRunner} and initialize a
* {@link TestContextManager} to provide Spring testing functionality to
Expand All @@ -113,6 +134,7 @@ public SpringJUnit4ClassRunner(Class<?> clazz) throws InitializationError {
if (logger.isDebugEnabled()) {
logger.debug("SpringJUnit4ClassRunner constructor called with [" + clazz + "].");
}
ensureSpringRulesAreNotPresent(clazz);
this.testContextManager = createTestContextManager(clazz);
}

Expand Down Expand Up @@ -166,7 +188,7 @@ public void run(RunNotifier notifier) {

/**
* Wrap the {@link Statement} returned by the parent implementation with a
* {@link RunBeforeTestClassCallbacks} statement, thus preserving the
* {@code RunBeforeTestClassCallbacks} statement, thus preserving the
* default JUnit functionality while adding support for the Spring TestContext
* Framework.
* @see RunBeforeTestClassCallbacks
Expand All @@ -179,7 +201,7 @@ protected Statement withBeforeClasses(Statement statement) {

/**
* Wrap the {@link Statement} returned by the parent implementation with a
* {@link RunAfterTestClassCallbacks} statement, thus preserving the default
* {@code RunAfterTestClassCallbacks} statement, thus preserving the default
* JUnit functionality while adding support for the Spring TestContext Framework.
* @see RunAfterTestClassCallbacks
*/
Expand Down Expand Up @@ -393,7 +415,7 @@ protected long getSpringTimeout(FrameworkMethod frameworkMethod) {

/**
* Wrap the {@link Statement} returned by the parent implementation with a
* {@link RunBeforeTestMethodCallbacks} statement, thus preserving the
* {@code RunBeforeTestMethodCallbacks} statement, thus preserving the
* default functionality while adding support for the Spring TestContext
* Framework.
* @see RunBeforeTestMethodCallbacks
Expand All @@ -407,7 +429,7 @@ protected Statement withBefores(FrameworkMethod frameworkMethod, Object testInst

/**
* Wrap the {@link Statement} returned by the parent implementation with a
* {@link RunAfterTestMethodCallbacks} statement, thus preserving the
* {@code RunAfterTestMethodCallbacks} statement, thus preserving the
* default functionality while adding support for the Spring TestContext
* Framework.
* @see RunAfterTestMethodCallbacks
Expand All @@ -423,10 +445,10 @@ protected Statement withAfters(FrameworkMethod frameworkMethod, Object testInsta
* Return a {@link Statement} that potentially repeats the execution of
* the {@code next} statement.
* <p>Supports Spring's {@link Repeat @Repeat} annotation by returning a
* {@link SpringRepeat} statement initialized with the configured repeat
* {@code SpringRepeat} statement initialized with the configured repeat
* count (if greater than {@code 1}); otherwise, the supplied statement
* is returned unmodified.
* @return either a {@link SpringRepeat} or the supplied {@link Statement}
* @return either a {@code SpringRepeat} or the supplied {@code Statement}
* as appropriate
* @see SpringRepeat
*/
Expand Down
Loading

0 comments on commit d1b1c4f

Please sign in to comment.