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 @@ + + + + + + + + diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/JUnitRulesInjectionTest.java b/spring-test/src/test/java/org/springframework/test/context/junit4/JUnitRulesInjectionTest.java new file mode 100644 index 000000000000..1d63fa62e6bc --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/junit4/JUnitRulesInjectionTest.java @@ -0,0 +1,88 @@ +/* + * 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.assertNotNull; + +import java.util.Arrays; +import java.util.Collection; + +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.MethodRule; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.tests.sample.beans.Pet; + + +/** + * Verifies that injection works correctly for rule based JUnit 4 tests. + * + * @author Philippe Marschall + * @since 3.2.2 + */ +@ContextConfiguration +@RunWith(Parameterized.class) +public class JUnitRulesInjectionTest { + + @ClassRule + public static final SpringJUnitClassRule CLASS_RULE = new SpringJUnitClassRule(); + + @Rule + public MethodRule methodRule = new SpringJUnitMethodRule(CLASS_RULE); + + private String parameter; + + @Autowired + private ApplicationContext applicationContext; + + @Autowired + private Pet pet; + + public JUnitRulesInjectionTest(final String parameter) { + this.parameter = parameter; + } + + @Test + public void firstMethod() { + assertNotNull("application context hasn't been injected", this.applicationContext); + assertNotNull("pet hasn't been injected", this.pet); + assertNotNull("parameter has not been set", this.parameter); + } + + @Test + public void secondMethod() { + assertNotNull("application context hasn't been injected", this.applicationContext); + assertNotNull("pet hasn't been injected", this.pet); + assertNotNull("parameter has not been set", this.parameter); + } + + @Parameters + public static Collection testData() { + return Arrays.asList(new Object[][] {// + // + { "first" },// + { "second" } // + }); + } + +} diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/TestExecutionListenersRuleTest.java b/spring-test/src/test/java/org/springframework/test/context/junit4/TestExecutionListenersRuleTest.java new file mode 100644 index 000000000000..8461bd7ae3f7 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/junit4/TestExecutionListenersRuleTest.java @@ -0,0 +1,111 @@ +/* + * 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 org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.MethodRule; +import org.springframework.test.context.TestContext; +import org.springframework.test.context.TestExecutionListener; +import org.springframework.test.context.TestExecutionListeners; + + +/** + * Verifies that test execution listeners work correctly for rule based + * JUnit 4 tests. + * + * @author Philippe Marschall + * @since 3.2.2 + */ +@TestExecutionListeners(TestExecutionListenersRuleTest.InnerTestExecutionListener.class) +public class TestExecutionListenersRuleTest { + + @ClassRule + public static final SpringJUnitClassRule CLASS_RULE = new SpringJUnitClassRule(); + + @Rule + public MethodRule methodRule = new SpringJUnitMethodRule(CLASS_RULE); + + private static int beforeTestClassRun = 0; + private static int prepareTestInstanceRun = 0; + private static int beforeTestMethodRun = 0; + private static int afterTestMethodRun = 0; + private static int afterTestClassRun = 0; + + @BeforeClass + public static void verifyBeforeClass() { + assertEquals("beforeTestClassRun", 1, beforeTestClassRun); + assertEquals("prepareTestInstanceRun", 0, prepareTestInstanceRun); + assertEquals("beforeTestMethodRun", 0, beforeTestMethodRun); + assertEquals("afterTestMethodRun", 0, afterTestMethodRun); + assertEquals("afterTestClassRun", 0, afterTestClassRun); + } + + @Test + public void verifyTest() { + assertEquals("beforeTestClassRun", 1, beforeTestClassRun); + assertEquals("prepareTestInstanceRun", 1, prepareTestInstanceRun); + assertEquals("beforeTestMethodRun", 1, beforeTestMethodRun); + assertEquals("afterTestMethodRun", 0, afterTestMethodRun); + assertEquals("afterTestClassRun", 0, afterTestClassRun); + } + + @AfterClass + public static void verifyAfterClass() { + assertEquals("beforeTestClassRun", 1, beforeTestClassRun); + assertEquals("prepareTestInstanceRun", 1, prepareTestInstanceRun); + assertEquals("beforeTestMethodRun", 1, beforeTestMethodRun); + assertEquals("afterTestMethodRun", 1, afterTestMethodRun); + assertEquals("afterTestClassRun", 0, afterTestClassRun); + } + + + static class InnerTestExecutionListener implements TestExecutionListener { + + @Override + public void beforeTestClass(TestContext testContext) throws Exception { + beforeTestClassRun += 1; + } + + @Override + public void prepareTestInstance(TestContext testContext) throws Exception { + prepareTestInstanceRun += 1; + } + + @Override + public void beforeTestMethod(TestContext testContext) throws Exception { + beforeTestMethodRun += 1; + } + + @Override + public void afterTestMethod(TestContext testContext) throws Exception { + afterTestMethodRun += 1; + } + + @Override + public void afterTestClass(TestContext testContext) throws Exception { + afterTestClassRun += 1; + } + + } + +}