Skip to content

Commit af6ca85

Browse files
Add '@EnabledIfDockerAvailable' JUnit 5 annotation (#8636)
Fixes #8613 --------- Co-authored-by: Eddú Meléndez Gonzales <[email protected]>
1 parent ec9651e commit af6ca85

File tree

5 files changed

+132
-7
lines changed

5 files changed

+132
-7
lines changed
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package org.testcontainers.junit.jupiter;
2+
3+
import org.testcontainers.DockerClientFactory;
4+
5+
class DockerAvailableDetector {
6+
7+
public boolean isDockerAvailable() {
8+
try {
9+
DockerClientFactory.instance().client();
10+
return true;
11+
} catch (Throwable ex) {
12+
return false;
13+
}
14+
}
15+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package org.testcontainers.junit.jupiter;
2+
3+
import org.junit.jupiter.api.extension.ExtendWith;
4+
5+
import java.lang.annotation.Documented;
6+
import java.lang.annotation.ElementType;
7+
import java.lang.annotation.Retention;
8+
import java.lang.annotation.RetentionPolicy;
9+
import java.lang.annotation.Target;
10+
11+
/**
12+
* {@code EnabledIfDockerAvailable} is a JUnit Jupiter extension to enable tests only if Docker is available.
13+
*/
14+
@Target({ ElementType.TYPE, ElementType.METHOD })
15+
@Retention(RetentionPolicy.RUNTIME)
16+
@Documented
17+
@ExtendWith(EnabledIfDockerAvailableCondition.class)
18+
public @interface EnabledIfDockerAvailable {
19+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package org.testcontainers.junit.jupiter;
2+
3+
import org.junit.jupiter.api.extension.ConditionEvaluationResult;
4+
import org.junit.jupiter.api.extension.ExecutionCondition;
5+
import org.junit.jupiter.api.extension.ExtensionConfigurationException;
6+
import org.junit.jupiter.api.extension.ExtensionContext;
7+
import org.junit.platform.commons.support.AnnotationSupport;
8+
9+
import java.util.Optional;
10+
11+
public class EnabledIfDockerAvailableCondition implements ExecutionCondition {
12+
13+
private final DockerAvailableDetector dockerDetector = new DockerAvailableDetector();
14+
15+
@Override
16+
public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) {
17+
return findAnnotation(context)
18+
.map(this::evaluate)
19+
.orElseThrow(() -> new ExtensionConfigurationException("@EnabledIfDockerAvailable not found"));
20+
}
21+
22+
boolean isDockerAvailable() {
23+
return this.dockerDetector.isDockerAvailable();
24+
}
25+
26+
private ConditionEvaluationResult evaluate(EnabledIfDockerAvailable testcontainers) {
27+
if (isDockerAvailable()) {
28+
return ConditionEvaluationResult.enabled("Docker is available");
29+
}
30+
return ConditionEvaluationResult.disabled("Docker is not available");
31+
}
32+
33+
private Optional<EnabledIfDockerAvailable> findAnnotation(ExtensionContext context) {
34+
Optional<ExtensionContext> current = Optional.of(context);
35+
while (current.isPresent()) {
36+
Optional<EnabledIfDockerAvailable> enabledIfDockerAvailable = AnnotationSupport.findAnnotation(
37+
current.get().getRequiredTestClass(),
38+
EnabledIfDockerAvailable.class
39+
);
40+
if (enabledIfDockerAvailable.isPresent()) {
41+
return enabledIfDockerAvailable;
42+
}
43+
current = current.get().getParent();
44+
}
45+
return Optional.empty();
46+
}
47+
}

modules/junit-jupiter/src/main/java/org/testcontainers/junit/jupiter/TestcontainersExtension.java

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
import org.junit.platform.commons.support.HierarchyTraversalMode;
1717
import org.junit.platform.commons.support.ModifierSupport;
1818
import org.junit.platform.commons.support.ReflectionSupport;
19-
import org.testcontainers.DockerClientFactory;
2019
import org.testcontainers.lifecycle.Startable;
2120
import org.testcontainers.lifecycle.Startables;
2221
import org.testcontainers.lifecycle.TestDescription;
@@ -42,6 +41,8 @@ public class TestcontainersExtension
4241

4342
private static final String LOCAL_LIFECYCLE_AWARE_CONTAINERS = "localLifecycleAwareContainers";
4443

44+
private final DockerAvailableDetector dockerDetector = new DockerAvailableDetector();
45+
4546
@Override
4647
public void beforeAll(ExtensionContext context) {
4748
Class<?> testClass = context
@@ -192,12 +193,7 @@ private ConditionEvaluationResult evaluate(Testcontainers testcontainers) {
192193
}
193194

194195
boolean isDockerAvailable() {
195-
try {
196-
DockerClientFactory.instance().client();
197-
return true;
198-
} catch (Throwable ex) {
199-
return false;
200-
}
196+
return this.dockerDetector.isDockerAvailable();
201197
}
202198

203199
private Set<Object> collectParentTestInstances(final ExtensionContext context) {
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package org.testcontainers.junit.jupiter;
2+
3+
import org.junit.jupiter.api.Test;
4+
import org.junit.jupiter.api.extension.ConditionEvaluationResult;
5+
import org.junit.jupiter.api.extension.ExtensionContext;
6+
7+
import static org.assertj.core.api.Assertions.assertThat;
8+
import static org.mockito.Mockito.mock;
9+
import static org.mockito.Mockito.when;
10+
11+
public class EnabledIfDockerAvailableTests {
12+
13+
@Test
14+
void whenDockerIsAvailableTestsAreEnabled() {
15+
ConditionEvaluationResult result = new TestEnabledIfDockerAvailableCondition(true)
16+
.evaluateExecutionCondition(extensionContext(DisabledWithoutDocker.class));
17+
assertThat(result.isDisabled()).isFalse();
18+
}
19+
20+
@Test
21+
void whenDockerIsUnavailableTestsAreDisabled() {
22+
ConditionEvaluationResult result = new TestEnabledIfDockerAvailableCondition(false)
23+
.evaluateExecutionCondition(extensionContext(DisabledWithoutDocker.class));
24+
assertThat(result.isDisabled()).isTrue();
25+
}
26+
27+
private ExtensionContext extensionContext(Class clazz) {
28+
ExtensionContext extensionContext = mock(ExtensionContext.class);
29+
when(extensionContext.getRequiredTestClass()).thenReturn(clazz);
30+
return extensionContext;
31+
}
32+
33+
@EnabledIfDockerAvailable
34+
static final class DisabledWithoutDocker {}
35+
36+
static final class TestEnabledIfDockerAvailableCondition extends EnabledIfDockerAvailableCondition {
37+
38+
private final boolean dockerAvailable;
39+
40+
private TestEnabledIfDockerAvailableCondition(boolean dockerAvailable) {
41+
this.dockerAvailable = dockerAvailable;
42+
}
43+
44+
boolean isDockerAvailable() {
45+
return dockerAvailable;
46+
}
47+
}
48+
}

0 commit comments

Comments
 (0)