diff --git a/spring-test/src/main/java/org/springframework/test/context/junit/jupiter/AbstractExpressionEvaluatingCondition.java b/spring-test/src/main/java/org/springframework/test/context/junit/jupiter/AbstractExpressionEvaluatingCondition.java new file mode 100644 index 00000000000..c8f7305c8f1 --- /dev/null +++ b/spring-test/src/main/java/org/springframework/test/context/junit/jupiter/AbstractExpressionEvaluatingCondition.java @@ -0,0 +1,167 @@ +/* + * Copyright 2002-2016 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.junit.jupiter; + +import java.lang.annotation.Annotation; +import java.lang.reflect.AnnotatedElement; +import java.util.Optional; +import java.util.function.Function; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.junit.jupiter.api.extension.ConditionEvaluationResult; +import org.junit.jupiter.api.extension.ContainerExecutionCondition; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.TestExecutionCondition; + +import org.springframework.beans.factory.config.BeanExpressionContext; +import org.springframework.beans.factory.config.BeanExpressionResolver; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.core.annotation.AnnotatedElementUtils; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +/** + * Abstract base class for implementations of {@link ContainerExecutionCondition} + * and {@link TestExecutionCondition} that evaluate expressions configured via + * annotations to determine if a container or test is enabled. + * + *
Expressions can be any of the following. + * + *
#{systemProperties['os.name'].toLowerCase().contains('mac')}
+ * ${smoke.tests.enabled}
+ * true+ *
Any attempt to use {@code DisabledIfCondition} without the presence of - * {@link DisabledIf @DisabledIf} will result in an {@link IllegalStateException}. + *
Any attempt to use the {@code DisabledIfCondition} without the presence of
+ * {@link DisabledIf @DisabledIf} will result in an enabled
+ * {@link ConditionEvaluationResult}.
*
* @author Sam Brannen
* @author Tadaya Tsuyukubo
* @since 5.0
- * @see org.springframework.test.context.junit.jupiter.DisabledIf
- * @see org.springframework.test.context.junit.jupiter.SpringExtension
+ * @see DisabledIf
+ * @see EnabledIf
+ * @see SpringExtension
*/
-public class DisabledIfCondition implements ContainerExecutionCondition, TestExecutionCondition {
-
- private static final Log logger = LogFactory.getLog(DisabledIfCondition.class);
-
+public class DisabledIfCondition extends AbstractExpressionEvaluatingCondition {
/**
* Containers are disabled if {@code @DisabledIf} is present on the test class
@@ -77,65 +60,8 @@ public class DisabledIfCondition implements ContainerExecutionCondition, TestExe
return evaluateDisabledIf(context);
}
- private ConditionEvaluationResult evaluateDisabledIf(ExtensionContext extensionContext) {
- AnnotatedElement element = extensionContext.getElement().get();
- Optional When applied at the class level, all test methods within that class
+ * are automatically enabled by default as well.
+ *
+ * For basic examples, see the Javadoc for {@link #expression}.
+ *
+ * This annotation may be used as a meta-annotation to create
+ * custom composed annotations. For example, a custom
+ * {@code @EnabledOnMac} annotation can be created as follows.
+ *
+ * If the expression evaluates to {@link Boolean#TRUE} or a {@link String}
+ * equal to {@code "true"} (ignoring case), the test will be enabled.
+ *
+ * Expressions can be any of the following.
+ *
+ * Note, however, that a text literal which is not the result of
+ * dynamic resolution of a property placeholder is of zero practical value
+ * since {@code @EnabledIf("false")} is equivalent to {@code @Disabled}
+ * and {@code @EnabledIf("true")} is logically meaningless.
+ *
+ * @see #reason
+ * @see #value
+ */
+ @AliasFor("value")
+ String expression() default "";
+
+ /**
+ * The reason this test is enabled.
+ *
+ * @see #expression
+ */
+ String reason() default "";
+
+}
diff --git a/spring-test/src/main/java/org/springframework/test/context/junit/jupiter/EnabledIfCondition.java b/spring-test/src/main/java/org/springframework/test/context/junit/jupiter/EnabledIfCondition.java
new file mode 100644
index 00000000000..aaba6b1f7ab
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/context/junit/jupiter/EnabledIfCondition.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2002-2016 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.junit.jupiter;
+
+import org.junit.jupiter.api.extension.ConditionEvaluationResult;
+import org.junit.jupiter.api.extension.ContainerExecutionCondition;
+import org.junit.jupiter.api.extension.ContainerExtensionContext;
+import org.junit.jupiter.api.extension.ExtensionContext;
+import org.junit.jupiter.api.extension.TestExecutionCondition;
+import org.junit.jupiter.api.extension.TestExtensionContext;
+
+/**
+ * {@code EnabledIfCondition} is a composite {@link ContainerExecutionCondition}
+ * and {@link TestExecutionCondition} that supports the {@link EnabledIf @EnabledIf}
+ * annotation when using the Spring TestContext Framework in conjunction
+ * with JUnit 5's Jupiter programming model.
+ *
+ * Any attempt to use the {@code EnabledIfCondition} without the presence of
+ * {@link EnabledIf @EnabledIf} will result in an enabled
+ * {@link ConditionEvaluationResult}.
+ *
+ * @author Sam Brannen
+ * @since 5.0
+ * @see EnabledIf
+ * @see DisabledIf
+ * @see SpringExtension
+ */
+public class EnabledIfCondition extends AbstractExpressionEvaluatingCondition {
+
+ /**
+ * Containers are enabled if {@code @EnabledIf} is present on the test class
+ * and the configured expression evaluates to {@code true}.
+ */
+ @Override
+ public ConditionEvaluationResult evaluate(ContainerExtensionContext context) {
+ return evaluateEnabledIf(context);
+ }
+
+ /**
+ * Tests are enabled if {@code @EnabledIf} is present on the test method
+ * and the configured expression evaluates to {@code true}.
+ */
+ @Override
+ public ConditionEvaluationResult evaluate(TestExtensionContext context) {
+ return evaluateEnabledIf(context);
+ }
+
+ private ConditionEvaluationResult evaluateEnabledIf(ExtensionContext context) {
+ return evaluateAnnotation(EnabledIf.class, EnabledIf::expression, EnabledIf::reason, true, context);
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/DisabledIfConditionTestCase.java b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/DisabledIfConditionTestCase.java
index 617a1f8747d..1104215e176 100644
--- a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/DisabledIfConditionTestCase.java
+++ b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/DisabledIfConditionTestCase.java
@@ -34,7 +34,6 @@ import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.endsWith;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.is;
-import static org.hamcrest.CoreMatchers.startsWith;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.jupiter.api.Assertions.assertAll;
import static org.junit.jupiter.api.Assertions.assertFalse;
@@ -61,10 +60,8 @@ class DisabledIfConditionTestCase {
@Test
void missingDisabledIf() {
- IllegalStateException exception = expectThrows(IllegalStateException.class,
- () -> condition.evaluate(buildExtensionContext("missingDisabledIf")));
-
- assertThat(exception.getMessage(), startsWith("@DisabledIf must be present"));
+ assertResult(condition.evaluate(buildExtensionContext("missingDisabledIf")), false,
+ endsWith("missingDisabledIf() is enabled since @DisabledIf is not present"));
}
@Test
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/DisabledIfTestCase.java b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/DisabledIfTestCase.java
index aee2a06cbf2..0f7c9e1c186 100644
--- a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/DisabledIfTestCase.java
+++ b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/DisabledIfTestCase.java
@@ -46,73 +46,73 @@ class DisabledIfTestCase {
@Test
@DisabledIf("true")
- void disabledByStringTrue() {
+ void disabledIfWithStringTrue() {
fail("This test must be disabled");
}
@Test
@DisabledIf(" true ")
- void disabledByStringTrueWithSurroundingWhitespace() {
+ void disabledIfWithStringTrueWithSurroundingWhitespace() {
fail("This test must be disabled");
}
@Test
@DisabledIf("TrUe")
- void disabledByStringTrueIgnoreCase() {
+ void disabledIfWithStringTrueIgnoreCase() {
fail("This test must be disabled");
}
@Test
@DisabledIf("${foo}")
- void disabledByPropertyPlaceholder() {
+ void disabledIfWithPropertyPlaceholder() {
fail("This test must be disabled");
}
@Test
@DisabledIf("\t${foo} ")
- void disabledByPropertyPlaceholderWithSurroundingWhitespace() {
+ void disabledIfWithPropertyPlaceholderWithSurroundingWhitespace() {
fail("This test must be disabled");
}
@Test
- @DisabledIf("#{T(java.lang.Boolean).TRUE}")
- void disabledBySpelBoolean() {
+ @DisabledIf("#{T(Boolean).TRUE}")
+ void disabledIfWithSpelBoolean() {
fail("This test must be disabled");
}
@Test
- @DisabledIf(" #{T(java.lang.Boolean).TRUE} ")
- void disabledBySpelBooleanWithSurroundingWhitespace() {
+ @DisabledIf(" #{T(Boolean).TRUE} ")
+ void disabledIfWithSpelBooleanWithSurroundingWhitespace() {
fail("This test must be disabled");
}
@Test
@DisabledIf("#{'tr' + 'ue'}")
- void disabledBySpelStringConcatenation() {
+ void disabledIfWithSpelStringConcatenation() {
fail("This test must be disabled");
}
@Test
@DisabledIf("#{6 * 7 == 42}")
- void disabledBySpelMathematicalComparison() {
+ void disabledIfWithSpelArithmeticComparison() {
fail("This test must be disabled");
}
@Test
@DisabledOnMac
- void disabledBySpelOsCheckInCustomComposedAnnotation() {
+ void disabledIfWithSpelOsCheckInCustomComposedAnnotation() {
assertFalse(System.getProperty("os.name").contains("Mac"), "This test must be disabled on Mac OS");
}
@Test
@DisabledIf("#{@booleanTrueBean}")
- void disabledBySpelBooleanTrueBean() {
+ void disabledIfWithSpelBooleanTrueBean() {
fail("This test must be disabled");
}
@Test
@DisabledIf("#{@stringTrueBean}")
- void disabledBySpelStringTrueBean() {
+ void disabledIfWithSpelStringTrueBean() {
fail("This test must be disabled");
}
@@ -128,12 +128,10 @@ class DisabledIfTestCase {
fail("This test must be disabled");
}
- // Even though method level condition is not disabling test, class level condition
- // should take precedence
@Test
@DisabledIf("false")
void bar() {
- fail("This test must be disabled");
+ fail("This test must be disabled due to class-level condition");
}
}
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/EnabledIfTestCase.java b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/EnabledIfTestCase.java
new file mode 100644
index 00000000000..13631320f68
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/EnabledIfTestCase.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright 2002-2016 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.junit.jupiter;
+
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.test.context.TestPropertySource;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
+
+/**
+ * Integration tests which verify support for {@link EnabledIf @EnabledIf}
+ * in conjunction with the {@link SpringExtension} in a JUnit 5 (Jupiter)
+ * environment.
+ *
+ * @author Tadaya Tsuyukubo
+ * @author Sam Brannen
+ * @since 5.0
+ * @see EnabledIfConditionTestCase
+ * @see EnabledIf
+ * @see SpringExtension
+ */
+class EnabledIfTestCase {
+
+ @SpringJUnitConfig(Config.class)
+ @TestPropertySource(properties = "foo = false")
+ @Nested
+ class EnabledIfOnMethodTestCase {
+
+ @Test
+ @EnabledIf("false")
+ void enabledIfWithStringFalse() {
+ fail("This test must be disabled");
+ }
+
+ @Test
+ @EnabledIf(" false ")
+ void enabledIfWithStringFalseWithSurroundingWhitespace() {
+ fail("This test must be disabled");
+ }
+
+ @Test
+ @EnabledIf("FaLsE")
+ void enabledIfWithStringFalseIgnoreCase() {
+ fail("This test must be disabled");
+ }
+
+ @Test
+ @EnabledIf("${foo}")
+ void enabledIfWithPropertyPlaceholder() {
+ fail("This test must be disabled");
+ }
+
+ @Test
+ @EnabledIf("\t${foo} ")
+ void enabledIfWithPropertyPlaceholderWithSurroundingWhitespace() {
+ fail("This test must be disabled");
+ }
+
+ @Test
+ @EnabledIf("#{T(Boolean).FALSE}")
+ void enabledIfWithSpelBoolean() {
+ fail("This test must be disabled");
+ }
+
+ @Test
+ @EnabledIf(" #{T(Boolean).FALSE} ")
+ void enabledIfWithSpelBooleanWithSurroundingWhitespace() {
+ fail("This test must be disabled");
+ }
+
+ @Test
+ @EnabledIf("#{'fal' + 'se'}")
+ void enabledIfWithSpelStringConcatenation() {
+ fail("This test must be disabled");
+ }
+
+ @Test
+ @EnabledIf("#{1 + 2 == 4}")
+ void enabledIfWithSpelArithmeticComparison() {
+ fail("This test must be disabled");
+ }
+
+ @Test
+ @EnabledOnMac
+ void enabledIfWithSpelOsCheckInCustomComposedAnnotation() {
+ String os = System.getProperty("os.name").toLowerCase();
+ assertTrue(os.contains("mac"), "This test must be enabled on Mac OS");
+ assertFalse(os.contains("win"), "This test must be disabled on Windows");
+ }
+
+ @Test
+ @EnabledIf("#{@booleanFalseBean}")
+ void enabledIfWithSpelBooleanFalseBean() {
+ fail("This test must be disabled");
+ }
+
+ @Test
+ @EnabledIf("#{@stringFalseBean}")
+ void enabledIfWithSpelStringFalseBean() {
+ fail("This test must be disabled");
+ }
+ }
+
+ @SpringJUnitConfig(Config.class)
+ @Nested
+ @EnabledIf("false")
+ class EnabledIfOnClassTestCase {
+
+ @Test
+ void foo() {
+ fail("This test must be disabled");
+ }
+
+ @Test
+ @EnabledIf("true")
+ void bar() {
+ fail("This test must be disabled due to class-level condition");
+ }
+ }
+
+ @Configuration
+ static class Config {
+
+ @Bean
+ Boolean booleanFalseBean() {
+ return Boolean.FALSE;
+ }
+
+ @Bean
+ String stringFalseBean() {
+ return "false";
+ }
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/EnabledOnMac.java b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/EnabledOnMac.java
new file mode 100644
index 00000000000..270d4634664
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/EnabledOnMac.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2002-2016 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.junit.jupiter;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Demo composed annotation for {@link EnabledIf @EnabledIf} that
+ * enables a test class or test method if the current operating system is
+ * Mac OS.
+ *
+ * @author Sam Brannen
+ * @since 5.0
+ */
+@Target({ ElementType.TYPE, ElementType.METHOD })
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@EnabledIf(expression = "#{systemProperties['os.name'].toLowerCase().contains('mac')}", reason = "Enabled on Mac OS")
+public @interface EnabledOnMac {
+}
+ * {@literal @}Target({ ElementType.TYPE, ElementType.METHOD })
+ * {@literal @}Retention(RetentionPolicy.RUNTIME)
+ * {@literal @}EnabledIf(
+ * expression = "#{systemProperties['os.name'].toLowerCase().contains('mac')}",
+ * reason = "Enabled on Mac OS"
+ * )
+ * public {@literal @}interface EnabledOnMac {}
+ *
+ *
+ * @author Sam Brannen
+ * @since 5.0
+ * @see SpringExtension
+ * @see DisabledIf
+ * @see org.junit.jupiter.api.Disabled
+ */
+@Target({ ElementType.TYPE, ElementType.METHOD })
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@ExtendWith(EnabledIfCondition.class)
+public @interface EnabledIf {
+
+ /**
+ * Alias for {@link #expression}; only intended to be used if an
+ * explicit {@link #reason} is not provided.
+ *
+ * @see #expression
+ */
+ @AliasFor("expression")
+ String value() default "";
+
+ /**
+ * The expression that will be evaluated to determine if the annotated test
+ * class or test method is enabled.
+ *
+ *
+ *
+ *
+ * @EnabledIf("#{systemProperties['os.name'].toLowerCase().contains('mac')}")
+ * @EnabledIf("${smoke.tests.enabled}")
+ * @EnabledIf("true")
+ *