diff --git a/spring-test/src/main/java/org/springframework/test/context/junit4/SpringJUnitClassRule.java b/spring-test/src/main/java/org/springframework/test/context/junit4/SpringJUnitClassRule.java new file mode 100644 index 000000000000..cb606fb67231 --- /dev/null +++ b/spring-test/src/main/java/org/springframework/test/context/junit4/SpringJUnitClassRule.java @@ -0,0 +1,149 @@ +/* + * Copyright 2004-2013 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.test.context.junit4; + +import static org.junit.Assume.assumeTrue; + +import org.junit.rules.TestRule; +import org.junit.runner.Description; +import org.junit.runners.model.Statement; +import org.springframework.test.annotation.ProfileValueUtils; +import org.springframework.test.context.TestContextManager; + + +/** + *
+ * {@code SpringJUnitClassRule} is a custom {@link TestRule} which provides + * functionality of the Spring TestContext Framework to standard + * JUnit 4.9+ tests by means of the {@link TestContextManager} and associated + * support classes and annotations. + *
+ *+ * Compared to {@link SpringJUnit4ClassRunner} the rule based JUnit support + * has the advantage that it is independent of the runner and can therefore + * be combined with existing 3rd party runners like {@code Parameterized}. + *
+ *+ * However to achieve the same functionality as {@link SpringJUnit4ClassRunner} + * {@code SpringJUnitClassRule} has to be combined with + * {@link SpringJUnitMethodRule} as {@code SpringJUnitClassRule} only provides + * the class level features of {@link SpringJUnit4ClassRunner}. + *
+ * + *+ * The following example shows you how to use {@code SpringJUnitClassRule}. + *
+ *
+ * public class ExampleTest {
+ *
+ * @ClassRule
+ * public static final SpringJUnitClassRule CLASS_RULE = new SpringJUnitClassRule();
+ *
+ * @Rule
+ * public MethodRule methodRule = new SpringJUnitMethodRule(CLASS_RULE);
+ * }
+ *
+ *
+ * @author Philippe Marschall
+ * @since 3.2.2
+ * @see SpringJUnit4ClassRunner
+ * @see TestContextManager
+ * @see SpringJUnitMethodRule
+ */
+public class SpringJUnitClassRule implements TestRule {
+
+ // volatile since SpringJUnitMethodRule can potentially access it from a
+ // different thread depending on the runner.
+ private volatile TestContextManager testContextManager;
+
+ /**
+ * Get the {@link TestContextManager} associated with this runner.
+ * Will be {@code null} until this class is called by the JUnit framework.
+ */
+ protected final TestContextManager getTestContextManager() {
+ return this.testContextManager;
+ }
+
+
+ /**
+ * Creates a new {@link TestContextManager} for the supplied test class and
+ * the configured default {@code ContextLoader} class name.
+ * Can be overridden by subclasses.
+ * @param clazz the test class to be managed
+ * @see #getDefaultContextLoaderClassName(Class)
+ */
+ protected TestContextManager createTestContextManager(Class> clazz) {
+ return new TestContextManager(clazz, getDefaultContextLoaderClassName(clazz));
+ }
+
+
+ /**
+ * Get the name of the default {@code ContextLoader} class to use for
+ * the supplied test class. The named class will be used if the test class
+ * does not explicitly declare a {@code ContextLoader} class via the
+ * {@code @ContextConfiguration} annotation.
+ * The default implementation returns {@code null}, thus implying use + * of the standard default {@code ContextLoader} class name. + * Can be overridden by subclasses. + * @param clazz the test class + * @return {@code null} + */ + protected String getDefaultContextLoaderClassName(Class> clazz) { + return null; + } + + + /** + * Check whether the test is enabled in the first place. This prevents + * classes with a non-matching {@code @IfProfileValue} annotation + * from running altogether, even skipping the execution of + * {@code prepareTestInstance()} {@code TestExecutionListener} + * methods. + * Creates the {@link TestContextManager} and runs it's before and after + * class methods. + * + * @see ProfileValueUtils#isTestEnabledInThisEnvironment(Class) + * @see org.springframework.test.annotation.IfProfileValue + * @see org.springframework.test.context.TestExecutionListener + * @see #createTestContextManager(Class) + * @see TestContextManager#beforeTestClass() + * @see TestContextManager#afterTestClass() + */ + @Override + public Statement apply(final Statement base, final Description description) { + return new Statement() { + + @Override + public void evaluate() throws Throwable { + Class> testClass = description.getTestClass(); + boolean profileIsActive = ProfileValueUtils.isTestEnabledInThisEnvironment(testClass); + assumeTrue("required profile not active", profileIsActive); + testContextManager = createTestContextManager(testClass); + testContextManager.beforeTestClass(); + try { + base.evaluate(); + } finally { + testContextManager.afterTestClass(); + // make test context manager eligible for garbage collection + testContextManager = null; + } + + } + }; + } + +} diff --git a/spring-test/src/main/java/org/springframework/test/context/junit4/SpringJUnitMethodRule.java b/spring-test/src/main/java/org/springframework/test/context/junit4/SpringJUnitMethodRule.java new file mode 100644 index 000000000000..0b73bb8a3112 --- /dev/null +++ b/spring-test/src/main/java/org/springframework/test/context/junit4/SpringJUnitMethodRule.java @@ -0,0 +1,97 @@ +/* + * Copyright 2004-2013 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.test.context.junit4; + +import static org.junit.Assume.assumeTrue; + +import java.lang.reflect.Method; + +import org.junit.rules.MethodRule; +import org.junit.runners.model.FrameworkMethod; +import org.junit.runners.model.Statement; +import org.springframework.test.annotation.ExpectedException; +import org.springframework.test.annotation.ProfileValueUtils; +import org.springframework.test.context.TestContextManager; + + +/** + *
+ * {@code MethodRule} is a custom {@link MethodRule} which together with + * {@link SpringJUnitClassRule} provides functionality of the Spring + * TestContext Framework to standard JUnit 4.9+ tests by means of the + * {@link TestContextManager} and associated support classes and annotations. + *
+ *+ * The only feature not supported of {@link SpringJUnit4ClassRunner} is + * {@link ExpectedException}. + *
+ * + * @author Philippe Marschall + * @since 3.2.2 + * @see SpringJUnitClassRule + */ +public class SpringJUnitMethodRule implements MethodRule { + + // Hold on to the class rule instead of the TestContextManager because + // the class rule "owns" the TestContextManager can releases it when no + // longer needed. + private final SpringJUnitClassRule classRule; + + /** + * Constructs a new {@code SpringJUnitMethodRule}. + * + * @param classRule the class rule, not {@code null}, + * the class rule has to be defined in the same test class + * where this {@link SpringJUnitMethodRule} instance is defined + */ + public SpringJUnitMethodRule(SpringJUnitClassRule classRule) { + this.classRule = classRule; + } + + /** + * Prepares the test instance (performs the injection) and runs the before + * and after test methods on the {@link TestContextManager}. + * + * @see TestContextManager#prepareTestInstance(Object) + * @see TestContextManager#beforeTestMethod(Object, Method) + * @see TestContextManager#afterTestMethod(Object, Method, Throwable) + */ + @Override + public Statement apply(final Statement base, final FrameworkMethod method, final Object target) { + return new Statement() { + + @Override + public void evaluate() throws Throwable { + Method testMethod = method.getMethod(); + boolean profileIsActive = ProfileValueUtils.isTestEnabledInThisEnvironment(testMethod, target.getClass()); + assumeTrue("required profile not active", profileIsActive); + TestContextManager testContextManager = classRule.getTestContextManager(); + testContextManager.prepareTestInstance(target); + testContextManager.beforeTestMethod(target, testMethod); + try { + base.evaluate(); + } + catch (Throwable t) { + testContextManager.afterTestMethod(target, testMethod, t); + throw t; + } + testContextManager.afterTestMethod(target, testMethod, null); + } + }; + } + +} diff --git a/spring-test/src/main/java/org/springframework/test/context/junit4/package-info.java b/spring-test/src/main/java/org/springframework/test/context/junit4/package-info.java index 64f30c8f8ac7..04187072b39b 100644 --- a/spring-test/src/main/java/org/springframework/test/context/junit4/package-info.java +++ b/spring-test/src/main/java/org/springframework/test/context/junit4/package-info.java @@ -1,6 +1,7 @@ /** *Support classes for ApplicationContext-based and transactional - * tests run with JUnit 4.5+ and the Spring TestContext Framework.
+ * tests run with JUnit 4.5+ and the Spring TestContext Framework. + * The rules need JUnit 4.9+ */ package org.springframework.test.context.junit4; diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/EnabledAndIgnoredSpringRuleTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/EnabledAndIgnoredSpringRuleTests.java new file mode 100644 index 000000000000..610202130214 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/junit4/EnabledAndIgnoredSpringRuleTests.java @@ -0,0 +1,98 @@ +/* + * Copyright 2004-2013 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.test.context.junit4; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Ignore; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.MethodRule; +import org.springframework.test.annotation.IfProfileValue; +import org.springframework.test.context.TestExecutionListeners; + + +/** + * Same as {@link EnabledAndIgnoredSpringRunnerTests} but for rule based tests. + * + * @author Philippe Marschall + * @since 3.2.2 + * @see EnabledAndIgnoredSpringRunnerTests + */ +@TestExecutionListeners( {}) +public class EnabledAndIgnoredSpringRuleTests { + + @ClassRule + public static final SpringJUnitClassRule CLASS_RULE = new SpringJUnitClassRule(); + + @Rule + public MethodRule methodRule = new SpringJUnitMethodRule(CLASS_RULE); + + protected static final String NAME = "EnabledAndIgnoredSpringRunnerTests.profile_value.name"; + + protected static final String VALUE = "enigma"; + + protected static int numTestsExecuted = 0; + + + @BeforeClass + public static void setProfileValue() { + numTestsExecuted = 0; + System.setProperty(NAME, VALUE); + } + + @AfterClass + public static void verifyNumTestsExecuted() { + assertEquals("Verifying the number of tests executed.", 3, numTestsExecuted); + } + + @Test + @IfProfileValue(name = NAME, value = VALUE + "X") + public void testIfProfileValueDisabled() { + numTestsExecuted++; + fail("The body of a disabled test should never be executed!"); + } + + @Test + @IfProfileValue(name = NAME, value = VALUE) + public void testIfProfileValueEnabledViaSingleValue() { + numTestsExecuted++; + } + + @Test + @IfProfileValue(name = NAME, values = { "foo", VALUE, "bar" }) + public void testIfProfileValueEnabledViaMultipleValues() { + numTestsExecuted++; + } + + @Test + public void testIfProfileValueNotConfigured() { + numTestsExecuted++; + } + + @Test + @Ignore + public void testJUnitIgnoreAnnotation() { + numTestsExecuted++; + fail("The body of an ignored test should never be executed!"); + } + +} diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/IgnoredOnClassLevelSpringRuleTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/IgnoredOnClassLevelSpringRuleTests.java new file mode 100644 index 000000000000..c76b8d4cbfda --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/junit4/IgnoredOnClassLevelSpringRuleTests.java @@ -0,0 +1,52 @@ +/* + * Copyright 2004-2013 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.test.context.junit4; + +import static org.junit.Assert.fail; + +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.MethodRule; +import org.springframework.test.annotation.IfProfileValue; +import org.springframework.test.context.TestExecutionListeners; + + +/** + * Same as {@link EnabledAndIgnoredSpringRuleTests} but on a class level. + * + * @author Philippe Marschall + * @since 3.2.2 + * @see EnabledAndIgnoredSpringRuleTests + */ +@TestExecutionListeners( {}) +@IfProfileValue(name = EnabledAndIgnoredSpringRuleTests.NAME, + value = EnabledAndIgnoredSpringRuleTests.VALUE + "X") +public class IgnoredOnClassLevelSpringRuleTests { + + @ClassRule + public static final SpringJUnitClassRule CLASS_RULE = new SpringJUnitClassRule(); + + @Rule + public MethodRule methodRule = new SpringJUnitMethodRule(CLASS_RULE); + + @Test + public void testIfProfileValueDisabled() { + fail("The body of a disabled test should never be executed!"); + } + +} diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/JUnitRulesInjectionTest-context.xml b/spring-test/src/test/java/org/springframework/test/context/junit4/JUnitRulesInjectionTest-context.xml new file mode 100644 index 000000000000..6d8ac23bcae6 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/junit4/JUnitRulesInjectionTest-context.xml @@ -0,0 +1,9 @@ + +