diff --git a/spring-test/src/main/java/org/springframework/test/context/junit/jupiter/DisabledIf.java b/spring-test/src/main/java/org/springframework/test/context/junit/jupiter/DisabledIf.java
new file mode 100644
index 00000000000..e5da0195723
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/context/junit/jupiter/DisabledIf.java
@@ -0,0 +1,111 @@
+/*
+ * 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;
+
+import org.junit.jupiter.api.extension.ExtendWith;
+
+import org.springframework.core.annotation.AliasFor;
+
+/**
+ * {@code @DisabledIf} is used to signal that the annotated test class or test
+ * method is disabled and should not be executed if the supplied
+ * {@link #expression} evaluates to {@code true}.
+ *
+ *
When applied at the class level, all test methods within that class
+ * are automatically disabled 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 @DisabledOnMac} annotation can be created as follows.
+ *
+ *
+ * {@literal @}Target({ ElementType.TYPE, ElementType.METHOD })
+ * {@literal @}Retention(RetentionPolicy.RUNTIME)
+ * {@literal @}DisabledIf(
+ * expression = "#{systemProperties['os.name'].toLowerCase().contains('mac')}",
+ * reason = "Disabled on Mac OS"
+ * )
+ * public {@literal @}interface DisabledOnMac {}
+ *
+ *
+ * @author Sam Brannen
+ * @author Tadaya Tsuyukubo
+ * @since 5.0
+ * @see SpringExtension
+ * @see org.junit.jupiter.api.Disabled
+ */
+@Target({ ElementType.TYPE, ElementType.METHOD })
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@ExtendWith(DisabledIfCondition.class)
+public @interface DisabledIf {
+
+ /**
+ * 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 disabled.
+ *
+ * If the expression evaluates to {@link Boolean#TRUE} or a {@link String}
+ * equal to {@code "true"} (ignoring case), the test will be disabled.
+ *
+ *
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 @DisabledIf("true")} is equivalent to {@code @Disabled}
+ * and {@code @DisabledIf("false")} is logically meaningless.
+ *
+ * @see #reason
+ * @see #value
+ */
+ @AliasFor("value")
+ String expression() default "";
+
+ /**
+ * The reason this test is disabled.
+ *
+ * @see #expression
+ */
+ String reason() default "";
+
+}
diff --git a/spring-test/src/main/java/org/springframework/test/context/junit/jupiter/DisabledIfCondition.java b/spring-test/src/main/java/org/springframework/test/context/junit/jupiter/DisabledIfCondition.java
new file mode 100644
index 00000000000..ada71169241
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/context/junit/jupiter/DisabledIfCondition.java
@@ -0,0 +1,137 @@
+/*
+ * 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 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.ContainerExtensionContext;
+import org.junit.jupiter.api.extension.ExtensionContext;
+import org.junit.jupiter.api.extension.TestExecutionCondition;
+import org.junit.jupiter.api.extension.TestExtensionContext;
+
+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;
+
+/**
+ * {@code DisabledIfCondition} is a composite {@link ContainerExecutionCondition}
+ * and {@link TestExecutionCondition} that supports the {@link DisabledIf @DisabledIf}
+ * annotation when using the Spring TestContext Framework in conjunction
+ * with JUnit 5's Jupiter programming model.
+ *
+ *
Any attempt to use {@code DisabledIfCondition} without the presence of
+ * {@link DisabledIf @DisabledIf} will result in an {@link IllegalStateException}.
+ *
+ * @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
+ */
+public class DisabledIfCondition implements ContainerExecutionCondition, TestExecutionCondition {
+
+ private static final Log logger = LogFactory.getLog(DisabledIfCondition.class);
+
+
+ /**
+ * Containers are disabled if {@code @DisabledIf} is present on the test class
+ * and the configured expression evaluates to {@code true}.
+ */
+ @Override
+ public ConditionEvaluationResult evaluate(ContainerExtensionContext context) {
+ return evaluateDisabledIf(context);
+ }
+
+ /**
+ * Tests are disabled if {@code @DisabledIf} is present on the test method
+ * and the configured expression evaluates to {@code true}.
+ */
+ @Override
+ public ConditionEvaluationResult evaluate(TestExtensionContext context) {
+ return evaluateDisabledIf(context);
+ }
+
+ private ConditionEvaluationResult evaluateDisabledIf(ExtensionContext extensionContext) {
+ AnnotatedElement element = extensionContext.getElement().get();
+ Optional disabledIf = findMergedAnnotation(element, DisabledIf.class);
+ Assert.state(disabledIf.isPresent(), () -> "@DisabledIf must be present on " + element);
+
+ String expression = disabledIf.get().expression().trim();
+
+ if (isDisabled(expression, extensionContext)) {
+ String reason = disabledIf.map(DisabledIf::reason).filter(StringUtils::hasText).orElseGet(
+ () -> String.format("%s is disabled because @DisabledIf(\"%s\") evaluated to true", element,
+ expression));
+ logger.info(reason);
+ return ConditionEvaluationResult.disabled(reason);
+ }
+ else {
+ String reason = String.format("%s is enabled because @DisabledIf(\"%s\") did not evaluate to true",
+ element, expression);
+ logger.debug(reason);
+ return ConditionEvaluationResult.enabled(reason);
+ }
+ }
+
+ private boolean isDisabled(String expression, ExtensionContext extensionContext) {
+ ApplicationContext applicationContext = SpringExtension.getApplicationContext(extensionContext);
+
+ if (!(applicationContext instanceof ConfigurableApplicationContext)) {
+ if (logger.isWarnEnabled()) {
+ String contextType = (applicationContext != null ? applicationContext.getClass().getName() : "null");
+ logger.warn(String.format("@DisabledIf(\"%s\") could not be evaluated on [%s] since the test " +
+ "ApplicationContext [%s] is not a ConfigurableApplicationContext",
+ expression, extensionContext.getElement(), contextType));
+ }
+ return false;
+ }
+
+ ConfigurableBeanFactory configurableBeanFactory = ((ConfigurableApplicationContext) applicationContext).getBeanFactory();
+ BeanExpressionResolver expressionResolver = configurableBeanFactory.getBeanExpressionResolver();
+ BeanExpressionContext beanExpressionContext = new BeanExpressionContext(configurableBeanFactory, null);
+
+ Object result = expressionResolver.evaluate(configurableBeanFactory.resolveEmbeddedValue(expression),
+ beanExpressionContext);
+
+ Assert.state((result instanceof Boolean || result instanceof String), () ->
+ String.format("@DisabledIf(\"%s\") must evaluate to a String or a Boolean, not %s", expression,
+ (result != null ? result.getClass().getName() : "null")));
+
+ boolean disabled = (result instanceof Boolean && ((Boolean) result).booleanValue()) ||
+ (result instanceof String && Boolean.parseBoolean((String) result));
+
+ return disabled;
+ }
+
+ private static Optional findMergedAnnotation(AnnotatedElement element,
+ Class annotationType) {
+ return Optional.ofNullable(AnnotatedElementUtils.findMergedAnnotation(element, annotationType));
+ }
+
+}
diff --git a/spring-test/src/main/java/org/springframework/test/context/junit/jupiter/SpringExtension.java b/spring-test/src/main/java/org/springframework/test/context/junit/jupiter/SpringExtension.java
index 8eef966a330..93d81bb1e82 100644
--- a/spring-test/src/main/java/org/springframework/test/context/junit/jupiter/SpringExtension.java
+++ b/spring-test/src/main/java/org/springframework/test/context/junit/jupiter/SpringExtension.java
@@ -51,6 +51,7 @@ import org.springframework.util.Assert;
*
* @author Sam Brannen
* @since 5.0
+ * @see org.springframework.test.context.junit.jupiter.DisabledIf
* @see org.springframework.test.context.junit.jupiter.SpringJUnitConfig
* @see org.springframework.test.context.junit.jupiter.web.SpringJUnitWebConfig
* @see org.springframework.test.context.TestContextManager
@@ -65,6 +66,7 @@ public class SpringExtension implements BeforeAllCallback, AfterAllCallback, Tes
*/
private static final Namespace namespace = Namespace.create(SpringExtension.class);
+
/**
* Delegates to {@link TestContextManager#beforeTestClass}.
*/
@@ -184,7 +186,7 @@ public class SpringExtension implements BeforeAllCallback, AfterAllCallback, Tes
* application context
* @see org.springframework.test.context.TestContext#getApplicationContext()
*/
- private ApplicationContext getApplicationContext(ExtensionContext context) {
+ static ApplicationContext getApplicationContext(ExtensionContext context) {
return getTestContextManager(context).getTestContext().getApplicationContext();
}
@@ -193,7 +195,7 @@ public class SpringExtension implements BeforeAllCallback, AfterAllCallback, Tes
* {@code ExtensionContext}.
* @return the {@code TestContextManager}; never {@code null}
*/
- private TestContextManager getTestContextManager(ExtensionContext context) {
+ private static TestContextManager getTestContextManager(ExtensionContext context) {
Assert.notNull(context, "ExtensionContext must not be null");
Class> testClass = context.getTestClass().get();
Store store = context.getStore(namespace);
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
new file mode 100644
index 00000000000..152bcbd3001
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/DisabledIfTestCase.java
@@ -0,0 +1,136 @@
+/*
+ * 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.*;
+
+/**
+ * Integration tests which verify support for {@link DisabledIf @DisabledIf}
+ * in conjunction with the {@link SpringExtension} in a JUnit 5 (Jupiter)
+ * environment.
+ *
+ * @author Tadaya Tsuyukubo
+ * @author Sam Brannen
+ * @since 5.0
+ * @see DisabledIf
+ * @see SpringExtension
+ */
+class DisabledIfTestCase {
+
+ @SpringJUnitConfig(Config.class)
+ @TestPropertySource(properties = "foo = true")
+ @Nested
+ class DisabledIfOnMethodTestCase {
+
+ @Test
+ @DisabledIf("true")
+ void disabledByStringTrue() {
+ fail("This test must be disabled");
+ }
+
+ @Test
+ @DisabledIf("TrUe")
+ void disabledByStringTrueIgnoreCase() {
+ fail("This test must be disabled");
+ }
+
+ @Test
+ @DisabledIf("${foo}")
+ void disabledByPropertyPlaceholder() {
+ fail("This test must be disabled");
+ }
+
+ @Test
+ @DisabledIf("#{T(java.lang.Boolean).TRUE}")
+ void disabledBySpelBoolean() {
+ fail("This test must be disabled");
+ }
+
+ @Test
+ @DisabledIf("#{'tr' + 'ue'}")
+ void disabledBySpelStringConcatenation() {
+ fail("This test must be disabled");
+ }
+
+ @Test
+ @DisabledIf("#{6 * 7 == 42}")
+ void disabledBySpelMathematicalComparison() {
+ fail("This test must be disabled");
+ }
+
+ @Test
+ @DisabledOnMac
+ void disabledBySpelOsCheckInCustomComposedAnnotation() {
+ assertFalse(System.getProperty("os.name").contains("Mac"), "This test must be disabled on Mac OS");
+ }
+
+ @Test
+ @DisabledIf("#{@booleanTrueBean}")
+ void disabledBySpelBooleanTrueBean() {
+ fail("This test must be disabled");
+ }
+
+ @Test
+ @DisabledIf("#{@stringTrueBean}")
+ void disabledBySpelStringTrueBean() {
+ fail("This test must be disabled");
+ }
+
+ }
+
+ @SpringJUnitConfig(Config.class)
+ @Nested
+ @DisabledIf("true")
+ class DisabledIfOnClassTestCase {
+
+ @Test
+ void foo() {
+ 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");
+ }
+
+ }
+
+ @Configuration
+ static class Config {
+
+ @Bean
+ Boolean booleanTrueBean() {
+ return Boolean.TRUE;
+ }
+
+ @Bean
+ String stringTrueBean() {
+ return "true";
+ }
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/DisabledOnMac.java b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/DisabledOnMac.java
new file mode 100644
index 00000000000..6a62db34012
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/DisabledOnMac.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 DisabledIf @DisabledIf} that
+ * disables 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
+@DisabledIf(expression = "#{systemProperties['os.name'].toLowerCase().contains('mac')}", reason = "Disabled on Mac OS")
+public @interface DisabledOnMac {
+}