Skip to content

Commit ae16e45

Browse files
committed
Support @⁠Nullable reasons in ConditionEvaluationResult APIs
Closes #4698
1 parent 495ef75 commit ae16e45

File tree

2 files changed

+132
-9
lines changed

2 files changed

+132
-9
lines changed

junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ConditionEvaluationResult.java

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import java.util.Optional;
1616

1717
import org.apiguardian.api.API;
18+
import org.jspecify.annotations.Nullable;
1819
import org.junit.platform.commons.util.StringUtils;
1920
import org.junit.platform.commons.util.ToStringBuilder;
2021

@@ -29,47 +30,57 @@ public class ConditionEvaluationResult {
2930
/**
3031
* Factory for creating <em>enabled</em> results.
3132
*
32-
* @param reason the reason why the container or test should be enabled
33+
* @param reason the reason why the container or test should be enabled; may
34+
* be {@code null} if the reason is unknown
3335
* @return an enabled {@code ConditionEvaluationResult} with the given reason
36+
* or an <em>empty</em> reason
3437
*/
35-
public static ConditionEvaluationResult enabled(String reason) {
38+
public static ConditionEvaluationResult enabled(@Nullable String reason) {
3639
return new ConditionEvaluationResult(true, reason);
3740
}
3841

3942
/**
4043
* Factory for creating <em>disabled</em> results.
4144
*
42-
* @param reason the reason why the container or test should be disabled
45+
* @param reason the reason why the container or test should be disabled; may
46+
* be {@code null} if the reason is unknown
4347
* @return a disabled {@code ConditionEvaluationResult} with the given reason
48+
* or an <em>empty</em> reason
4449
*/
45-
public static ConditionEvaluationResult disabled(String reason) {
50+
public static ConditionEvaluationResult disabled(@Nullable String reason) {
4651
return new ConditionEvaluationResult(false, reason);
4752
}
4853

4954
/**
5055
* Factory for creating <em>disabled</em> results with custom reasons
5156
* added by the user.
5257
*
53-
* @param reason the default reason why the container or test should be disabled
54-
* @param customReason the custom reason why the container or test should be disabled
58+
* @param reason the default reason why the container or test should be disabled;
59+
* may be {@code null} if the default reason is unknown
60+
* @param customReason the custom reason why the container or test should be
61+
* disabled; may be {@code null} if the custom reason is unknown
5562
* @return a disabled {@code ConditionEvaluationResult} with the given reasons
63+
* or an <em>empty</em> reason
5664
* @since 5.7
5765
*/
5866
@API(status = STABLE, since = "5.7")
59-
public static ConditionEvaluationResult disabled(String reason, String customReason) {
67+
public static ConditionEvaluationResult disabled(@Nullable String reason, @Nullable String customReason) {
6068
if (StringUtils.isBlank(customReason)) {
6169
return disabled(reason);
6270
}
71+
if (StringUtils.isBlank(reason)) {
72+
return disabled(customReason);
73+
}
6374
return disabled("%s ==> %s".formatted(reason, customReason));
6475
}
6576

6677
private final boolean enabled;
6778

6879
private final Optional<String> reason;
6980

70-
private ConditionEvaluationResult(boolean enabled, String reason) {
81+
private ConditionEvaluationResult(boolean enabled, @Nullable String reason) {
7182
this.enabled = enabled;
72-
this.reason = Optional.ofNullable(reason);
83+
this.reason = Optional.ofNullable(StringUtils.isBlank(reason) ? null : reason.strip());
7384
}
7485

7586
/**
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
/*
2+
* Copyright 2015-2025 the original author or authors.
3+
*
4+
* All rights reserved. This program and the accompanying materials are
5+
* made available under the terms of the Eclipse Public License v2.0 which
6+
* accompanies this distribution and is available at
7+
*
8+
* https://www.eclipse.org/legal/epl-v20.html
9+
*/
10+
11+
package org.junit.jupiter.api.condition;
12+
13+
import static org.assertj.core.api.Assertions.assertThat;
14+
15+
import java.lang.annotation.Retention;
16+
import java.lang.annotation.RetentionPolicy;
17+
18+
import org.jspecify.annotations.Nullable;
19+
import org.junit.jupiter.api.Test;
20+
import org.junit.jupiter.api.extension.ConditionEvaluationResult;
21+
import org.junit.jupiter.params.ParameterizedTest;
22+
import org.junit.jupiter.params.provider.NullSource;
23+
import org.junit.jupiter.params.provider.ValueSource;
24+
25+
/**
26+
* Unit tests for {@link ConditionEvaluationResult}.
27+
*
28+
* @since 6.0
29+
*/
30+
class ConditionEvaluationResultTests {
31+
32+
@Test
33+
void enabledWithReason() {
34+
var result = ConditionEvaluationResult.enabled("reason");
35+
36+
assertThat(result.isDisabled()).isFalse();
37+
assertThat(result.getReason()).contains("reason");
38+
assertThat(result).asString().isEqualTo("ConditionEvaluationResult [enabled = true, reason = 'reason']");
39+
}
40+
41+
@EmptyReasonsTest
42+
void enabledWithoutReason(@Nullable String reason) {
43+
var result = ConditionEvaluationResult.enabled(reason);
44+
45+
assertThat(result.isDisabled()).isFalse();
46+
assertThat(result.getReason()).isEmpty();
47+
assertThat(result).asString().isEqualTo("ConditionEvaluationResult [enabled = true, reason = '<unknown>']");
48+
}
49+
50+
@Test
51+
void disabledWithDefaultReason() {
52+
var result = ConditionEvaluationResult.disabled("default");
53+
54+
assertThat(result.isDisabled()).isTrue();
55+
assertThat(result.getReason()).contains("default");
56+
assertThat(result).asString().isEqualTo("ConditionEvaluationResult [enabled = false, reason = 'default']");
57+
}
58+
59+
@EmptyReasonsTest
60+
void disabledWithoutDefaultReason(@Nullable String reason) {
61+
var result = ConditionEvaluationResult.disabled(reason);
62+
63+
assertThat(result.isDisabled()).isTrue();
64+
assertThat(result.getReason()).isEmpty();
65+
assertThat(result).asString().isEqualTo("ConditionEvaluationResult [enabled = false, reason = '<unknown>']");
66+
}
67+
68+
@EmptyReasonsTest
69+
void disabledWithDefaultReasonButWithoutCustomReason(@Nullable String customReason) {
70+
var result = ConditionEvaluationResult.disabled("default", customReason);
71+
72+
assertThat(result.isDisabled()).isTrue();
73+
assertThat(result.getReason()).contains("default");
74+
assertThat(result).asString().isEqualTo("ConditionEvaluationResult [enabled = false, reason = 'default']");
75+
}
76+
77+
@EmptyReasonsTest
78+
void disabledWithoutDefaultReasonButWithCustomReason(@Nullable String reason) {
79+
var result = ConditionEvaluationResult.disabled(reason, "custom");
80+
81+
assertThat(result.isDisabled()).isTrue();
82+
assertThat(result.getReason()).contains("custom");
83+
assertThat(result).asString().isEqualTo("ConditionEvaluationResult [enabled = false, reason = 'custom']");
84+
}
85+
86+
@EmptyReasonsTest
87+
void disabledWithoutDefaultReasonOrCustomReason(@Nullable String reason) {
88+
// We intentionally use the reason as both the default and custom reason.
89+
var result = ConditionEvaluationResult.disabled(reason, reason);
90+
91+
assertThat(result.isDisabled()).isTrue();
92+
assertThat(result.getReason()).isEmpty();
93+
assertThat(result).asString().isEqualTo("ConditionEvaluationResult [enabled = false, reason = '<unknown>']");
94+
}
95+
96+
@Test
97+
void disabledWithDefaultReasonAndCustomReason() {
98+
var result = ConditionEvaluationResult.disabled("default", "custom");
99+
100+
assertThat(result.isDisabled()).isTrue();
101+
assertThat(result.getReason()).contains("default ==> custom");
102+
assertThat(result).asString().isEqualTo("ConditionEvaluationResult [enabled = false, reason = 'default ==> custom']");
103+
}
104+
105+
@Retention(RetentionPolicy.RUNTIME)
106+
@ParameterizedTest
107+
@NullSource
108+
@ValueSource(strings = { "", " ", " ", "\t", "\n" })
109+
@interface EmptyReasonsTest {
110+
}
111+
112+
}

0 commit comments

Comments
 (0)