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
index eefbd43bfc0..e5da0195723 100644
--- 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
@@ -16,43 +16,96 @@
package org.springframework.test.context.junit.jupiter;
-import org.springframework.core.annotation.AliasFor;
-
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;
+
/**
- * Disable JUnit 5(Jupiter) tests when evaluated condition returns "true"
- * that can be either case insensitive {@code String} or {@code Boolean#TRUE}.
+ * {@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 #condition()}.
+ * Alias for {@link #expression}; only intended to be used if an
+ * explicit {@link #reason} is not provided.
+ *
+ * @see #expression
*/
- @AliasFor("condition")
+ @AliasFor("expression")
String value() default "";
/**
- * Condition to disable test.
+ * The expression that will be evaluated to determine if the annotated test
+ * class or test method is disabled.
*
- * When case insensitive {@code String} "true" or {@code Boolean#TRUE} is returned,
- * annotated test method or class is disabled.
- *
SpEL expression can be used.
+ *
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 condition() default "";
+ 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 d3a52c90c39..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
@@ -16,39 +16,28 @@
package org.springframework.test.context.junit.jupiter;
-import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Constructor;
import java.lang.reflect.Executable;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
-import java.util.Optional;
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
import org.junit.jupiter.api.extension.AfterAllCallback;
import org.junit.jupiter.api.extension.AfterEachCallback;
import org.junit.jupiter.api.extension.AfterTestExecutionCallback;
import org.junit.jupiter.api.extension.BeforeAllCallback;
import org.junit.jupiter.api.extension.BeforeEachCallback;
import org.junit.jupiter.api.extension.BeforeTestExecutionCallback;
-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.ExtensionContext.Namespace;
import org.junit.jupiter.api.extension.ExtensionContext.Store;
import org.junit.jupiter.api.extension.ParameterContext;
import org.junit.jupiter.api.extension.ParameterResolver;
-import org.junit.jupiter.api.extension.TestExecutionCondition;
import org.junit.jupiter.api.extension.TestExtensionContext;
import org.junit.jupiter.api.extension.TestInstancePostProcessor;
import org.springframework.beans.factory.annotation.Autowired;
-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.test.context.TestContextManager;
import org.springframework.util.Assert;
@@ -61,17 +50,15 @@ import org.springframework.util.Assert;
* {@code @ExtendWith(SpringExtension.class)}.
*
* @author Sam Brannen
- * @author Tadaya Tsuyukubo
* @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
- * @see DisabledIf
*/
public class SpringExtension implements BeforeAllCallback, AfterAllCallback, TestInstancePostProcessor,
BeforeEachCallback, AfterEachCallback, BeforeTestExecutionCallback, AfterTestExecutionCallback,
- ParameterResolver,
- ContainerExecutionCondition, TestExecutionCondition {
+ ParameterResolver {
/**
* {@link Namespace} in which {@code TestContextManagers} are stored, keyed
@@ -79,11 +66,6 @@ public class SpringExtension implements BeforeAllCallback, AfterAllCallback, Tes
*/
private static final Namespace namespace = Namespace.create(SpringExtension.class);
- private static final ConditionEvaluationResult TEST_ENABLED = ConditionEvaluationResult.enabled(
- "@DisabledIf condition didn't match");
-
- private static final Log logger = LogFactory.getLog(SpringExtension.class);
-
/**
* Delegates to {@link TestContextManager#beforeTestClass}.
@@ -195,61 +177,6 @@ public class SpringExtension implements BeforeAllCallback, AfterAllCallback, Tes
return ParameterAutowireUtils.resolveDependency(parameter, testClass, applicationContext);
}
- @Override
- public ConditionEvaluationResult evaluate(ContainerExtensionContext context) {
- return evaluateDisabledIf(context);
- }
-
- @Override
- public ConditionEvaluationResult evaluate(TestExtensionContext context) {
- return evaluateDisabledIf(context);
- }
-
- private ConditionEvaluationResult evaluateDisabledIf(ExtensionContext extensionContext) {
- Optional element = extensionContext.getElement();
- if (!element.isPresent()) {
- return TEST_ENABLED;
- }
-
- DisabledIf disabledIf = AnnotatedElementUtils.findMergedAnnotation(element.get(), DisabledIf.class);
- if (disabledIf == null) {
- return TEST_ENABLED;
- }
-
- String condition = disabledIf.condition();
- if (condition.trim().length() == 0) {
- return TEST_ENABLED;
- }
-
- ApplicationContext applicationContext = getApplicationContext(extensionContext);
- if (!(applicationContext instanceof ConfigurableApplicationContext)) {
- return TEST_ENABLED;
- }
-
- ConfigurableBeanFactory configurableBeanFactory = ((ConfigurableApplicationContext) applicationContext)
- .getBeanFactory();
- BeanExpressionResolver expressionResolver = configurableBeanFactory.getBeanExpressionResolver();
- BeanExpressionContext beanExpressionContext = new BeanExpressionContext(configurableBeanFactory, null);
-
- Object result = expressionResolver
- .evaluate(configurableBeanFactory.resolveEmbeddedValue(condition), beanExpressionContext);
-
- if (result == null || !Boolean.valueOf(result.toString())) {
- return TEST_ENABLED;
- }
-
- String reason = disabledIf.reason();
- if (reason.trim().length() == 0) {
- String testTarget = extensionContext.getTestMethod().map(Method::getName)
- .orElseGet(() -> extensionContext.getTestClass().get().getSimpleName());
- reason = String.format("%s is disabled. condition=%s", testTarget, condition);
- }
-
- logger.info(String.format("%s is disabled. reason=%s", element.get(), reason));
- return ConditionEvaluationResult.disabled(reason);
-
- }
-
/**
* Get the {@link ApplicationContext} associated with the supplied
* {@code ExtensionContext}.
@@ -259,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();
}
@@ -268,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
index 21c11178db1..152bcbd3001 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
@@ -18,27 +18,27 @@ package org.springframework.test.context.junit.jupiter;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.extension.ExtendWith;
+
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
-import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestPropertySource;
-import static org.junit.jupiter.api.Assertions.fail;
+import static org.junit.jupiter.api.Assertions.*;
/**
- * Integration tests which demonstrate usage of {@link DisabledIf @DisabledIf}
- * enabled by {@link SpringExtension} in a JUnit 5 (Jupiter) environment.
+ * 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 {
- @ExtendWith(SpringExtension.class)
- @ContextConfiguration(classes = Config.class)
+ @SpringJUnitConfig(Config.class)
@TestPropertySource(properties = "foo = true")
@Nested
class DisabledIfOnMethodTestCase {
@@ -73,6 +73,18 @@ class DisabledIfTestCase {
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() {
@@ -87,8 +99,7 @@ class DisabledIfTestCase {
}
- @ExtendWith(SpringExtension.class)
- @ContextConfiguration(classes = Config.class)
+ @SpringJUnitConfig(Config.class)
@Nested
@DisabledIf("true")
class DisabledIfOnClassTestCase {
@@ -98,7 +109,8 @@ class DisabledIfTestCase {
fail("This test must be disabled");
}
- // Even though method level condition is not disabling test, class level condition should take precedence
+ // Even though method level condition is not disabling test, class level condition
+ // should take precedence
@Test
@DisabledIf("false")
void bar() {
@@ -109,6 +121,7 @@ class DisabledIfTestCase {
@Configuration
static class Config {
+
@Bean
Boolean booleanTrueBean() {
return Boolean.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 {
+}