diff --git a/modules/junit-jupiter/src/main/java/org/testcontainers/junit/jupiter/DockerAvailableDetector.java b/modules/junit-jupiter/src/main/java/org/testcontainers/junit/jupiter/DockerAvailableDetector.java new file mode 100644 index 00000000000..804568e7dfc --- /dev/null +++ b/modules/junit-jupiter/src/main/java/org/testcontainers/junit/jupiter/DockerAvailableDetector.java @@ -0,0 +1,15 @@ +package org.testcontainers.junit.jupiter; + +import org.testcontainers.DockerClientFactory; + +class DockerAvailableDetector { + + public boolean isDockerAvailable() { + try { + DockerClientFactory.instance().client(); + return true; + } catch (Throwable ex) { + return false; + } + } +} diff --git a/modules/junit-jupiter/src/main/java/org/testcontainers/junit/jupiter/EnabledIfDockerAvailable.java b/modules/junit-jupiter/src/main/java/org/testcontainers/junit/jupiter/EnabledIfDockerAvailable.java new file mode 100644 index 00000000000..6c78cbb7759 --- /dev/null +++ b/modules/junit-jupiter/src/main/java/org/testcontainers/junit/jupiter/EnabledIfDockerAvailable.java @@ -0,0 +1,19 @@ +package org.testcontainers.junit.jupiter; + +import org.junit.jupiter.api.extension.ExtendWith; + +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; + +/** + * {@code EnabledIfDockerAvailable} is a JUnit Jupiter extension to enable tests only if Docker is available. + */ +@Target({ ElementType.TYPE, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@ExtendWith(EnabledIfDockerAvailableCondition.class) +public @interface EnabledIfDockerAvailable { +} diff --git a/modules/junit-jupiter/src/main/java/org/testcontainers/junit/jupiter/EnabledIfDockerAvailableCondition.java b/modules/junit-jupiter/src/main/java/org/testcontainers/junit/jupiter/EnabledIfDockerAvailableCondition.java new file mode 100644 index 00000000000..17df85700aa --- /dev/null +++ b/modules/junit-jupiter/src/main/java/org/testcontainers/junit/jupiter/EnabledIfDockerAvailableCondition.java @@ -0,0 +1,47 @@ +package org.testcontainers.junit.jupiter; + +import org.junit.jupiter.api.extension.ConditionEvaluationResult; +import org.junit.jupiter.api.extension.ExecutionCondition; +import org.junit.jupiter.api.extension.ExtensionConfigurationException; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.platform.commons.support.AnnotationSupport; + +import java.util.Optional; + +public class EnabledIfDockerAvailableCondition implements ExecutionCondition { + + private final DockerAvailableDetector dockerDetector = new DockerAvailableDetector(); + + @Override + public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) { + return findAnnotation(context) + .map(this::evaluate) + .orElseThrow(() -> new ExtensionConfigurationException("@EnabledIfDockerAvailable not found")); + } + + boolean isDockerAvailable() { + return dockerDetector.isDockerAvailable(); + } + + private ConditionEvaluationResult evaluate(EnabledIfDockerAvailable testcontainers) { + if (isDockerAvailable()) { + return ConditionEvaluationResult.enabled("Docker is available"); + } + return ConditionEvaluationResult.disabled("Docker is not available"); + } + + private Optional findAnnotation(ExtensionContext context) { + Optional current = Optional.of(context); + while (current.isPresent()) { + Optional enabledIfDockerAvailable = AnnotationSupport.findAnnotation( + current.get().getRequiredTestClass(), + EnabledIfDockerAvailable.class + ); + if (enabledIfDockerAvailable.isPresent()) { + return enabledIfDockerAvailable; + } + current = current.get().getParent(); + } + return Optional.empty(); + } +} diff --git a/modules/junit-jupiter/src/main/java/org/testcontainers/junit/jupiter/TestcontainersExtension.java b/modules/junit-jupiter/src/main/java/org/testcontainers/junit/jupiter/TestcontainersExtension.java index ca99a2d2382..a0f74c2e5a5 100644 --- a/modules/junit-jupiter/src/main/java/org/testcontainers/junit/jupiter/TestcontainersExtension.java +++ b/modules/junit-jupiter/src/main/java/org/testcontainers/junit/jupiter/TestcontainersExtension.java @@ -16,7 +16,6 @@ import org.junit.platform.commons.support.HierarchyTraversalMode; import org.junit.platform.commons.support.ModifierSupport; import org.junit.platform.commons.support.ReflectionSupport; -import org.testcontainers.DockerClientFactory; import org.testcontainers.lifecycle.Startable; import org.testcontainers.lifecycle.Startables; import org.testcontainers.lifecycle.TestDescription; @@ -42,6 +41,8 @@ public class TestcontainersExtension private static final String LOCAL_LIFECYCLE_AWARE_CONTAINERS = "localLifecycleAwareContainers"; + private final DockerAvailableDetector dockerDetector = new DockerAvailableDetector(); + @Override public void beforeAll(ExtensionContext context) { Class testClass = context @@ -192,12 +193,7 @@ private ConditionEvaluationResult evaluate(Testcontainers testcontainers) { } boolean isDockerAvailable() { - try { - DockerClientFactory.instance().client(); - return true; - } catch (Throwable ex) { - return false; - } + return dockerDetector.isDockerAvailable(); } private Set collectParentTestInstances(final ExtensionContext context) { diff --git a/modules/junit-jupiter/src/test/java/org/testcontainers/junit/jupiter/EnabledIfDockerAvailableTests.java b/modules/junit-jupiter/src/test/java/org/testcontainers/junit/jupiter/EnabledIfDockerAvailableTests.java new file mode 100644 index 00000000000..455e94788f9 --- /dev/null +++ b/modules/junit-jupiter/src/test/java/org/testcontainers/junit/jupiter/EnabledIfDockerAvailableTests.java @@ -0,0 +1,48 @@ +package org.testcontainers.junit.jupiter; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ConditionEvaluationResult; +import org.junit.jupiter.api.extension.ExtensionContext; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class EnabledIfDockerAvailableTests { + + @Test + void whenDockerIsAvailableTestsAreEnabled() { + ConditionEvaluationResult result = new TestEnabledIfDockerAvailableCondition(true) + .evaluateExecutionCondition(extensionContext(DisabledWithoutDocker.class)); + assertThat(result.isDisabled()).isFalse(); + } + + @Test + void whenDockerIsUnavailableTestsAreDisabled() { + ConditionEvaluationResult result = new TestEnabledIfDockerAvailableCondition(false) + .evaluateExecutionCondition(extensionContext(DisabledWithoutDocker.class)); + assertThat(result.isDisabled()).isTrue(); + } + + private ExtensionContext extensionContext(Class clazz) { + ExtensionContext extensionContext = mock(ExtensionContext.class); + when(extensionContext.getRequiredTestClass()).thenReturn(clazz); + return extensionContext; + } + + @EnabledIfDockerAvailable + static final class DisabledWithoutDocker {} + + static final class TestEnabledIfDockerAvailableCondition extends EnabledIfDockerAvailableCondition { + + private final boolean dockerAvailable; + + private TestEnabledIfDockerAvailableCondition(boolean dockerAvailable) { + this.dockerAvailable = dockerAvailable; + } + + boolean isDockerAvailable() { + return dockerAvailable; + } + } +}