Skip to content

Commit bb0f68b

Browse files
committed
Align (Completable)Future asserts with their soft counterparts
* Do not proxy `isCompletedWithValueMatchingWithin` in order to align the behavior with `succeedsWithin` * Propagate assertion state for (Completable)Future asserts - This was already done for the soft assertions
1 parent bf3bbd5 commit bb0f68b

9 files changed

Lines changed: 249 additions & 9 deletions

File tree

assertj-core/src/main/java/org/assertj/core/api/AbstractCompletableFutureAssert.java

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -263,10 +263,7 @@ public SELF isCompletedWithValue(RESULT expected) {
263263
* @throws AssertionError if the actual {@code CompletableFuture} does not succeed within the given timeout with the satisfying value.
264264
*/
265265
public SELF isCompletedWithValueMatchingWithin(Predicate<RESULT> resultPredicate, Duration completionDuration) {
266-
RESULT actualResult = futures.assertSucceededWithin(info, actual, completionDuration);
267-
if (!resultPredicate.test(actualResult)) {
268-
throw Failures.instance().failure(info, shouldMatch(actualResult, resultPredicate, PredicateDescription.GIVEN));
269-
}
266+
succeedsWithin(completionDuration).matches(resultPredicate);
270267
return myself;
271268
}
272269

@@ -446,7 +443,7 @@ private ObjectAssert<RESULT> internalSucceedsWithin(Duration timeout) {
446443

447444
// introduced to be proxied for assumptions and soft assertions.
448445
protected ObjectAssert<RESULT> newObjectAssert(RESULT objectUnderTest) {
449-
return new ObjectAssert<>(objectUnderTest);
446+
return new ObjectAssert<>(objectUnderTest).withAssertionState(myself);
450447
}
451448

452449
/**

assertj-core/src/main/java/org/assertj/core/api/AbstractFutureAssert.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -451,12 +451,12 @@ private WithThrowable internalFailsWithin(long timeout, TimeUnit unit) {
451451

452452
private ObjectAssert<RESULT> internalSucceedsWithin(Duration timeout) {
453453
RESULT result = futures.assertSucceededWithin(info, actual, timeout);
454-
return assertThat(result);
454+
return assertThat(result).withAssertionState(myself);
455455
}
456456

457457
private ObjectAssert<RESULT> internalSucceedsWithin(long timeout, TimeUnit unit) {
458458
RESULT result = futures.assertSucceededWithin(info, actual, timeout, unit);
459-
return assertThat(result);
459+
return assertThat(result).withAssertionState(myself);
460460
}
461461

462462
}

assertj-core/src/main/java/org/assertj/core/api/SoftProxies.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ class SoftProxies {
9595
.or(named("removeCustomAssertRelatedElementsFromStackTraceIfNeeded"))
9696
.or(named("succeedsWithin"))
9797
.or(named("failsWithin"))
98+
.or(named("isCompletedWithValueMatchingWithin"))
9899
.or(named("usingComparator"))
99100
.or(named("usingDefaultComparator"))
100101
.or(named("usingElementComparator"))

assertj-core/src/test/java/org/assertj/core/api/SoftAssertions_future_Test.java

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import java.util.concurrent.CompletableFuture;
3030
import java.util.concurrent.CompletionStage;
3131
import java.util.concurrent.Future;
32+
import java.util.function.Predicate;
3233

3334
import org.junit.jupiter.api.BeforeEach;
3435
import org.junit.jupiter.api.DisplayName;
@@ -110,6 +111,20 @@ void should_not_collect_AssertionError_from_CompletableFuture_succeedsWithin_Dur
110111
assertThat(assertionError).hasMessageContaining("Cancelled");
111112
}
112113

114+
@Test
115+
void should_not_collect_AssertionError_from_CompletableFuture_isCompletedWithValueMatchingWithin_Duration() {
116+
// GIVEN
117+
CompletableFuture<String> future = new CompletableFuture<>();
118+
future.cancel(false);
119+
// WHEN
120+
var assertionError = expectAssertionError(() -> softly.assertThat(future)
121+
.isCompletedWithValueMatchingWithin(Predicate.isEqual("test"),
122+
TEN_MILLIS));
123+
// THEN
124+
assertThat(softly.errorsCollected()).isEmpty();
125+
assertThat(assertionError).hasMessageContaining("Cancelled");
126+
}
127+
113128
@Test
114129
void should_not_collect_AssertionError_from_Future_succeedsWithin() {
115130
// GIVEN
@@ -235,6 +250,94 @@ void should_only_collect_error_from_chained_assertions_performed_after_Completab
235250
assertThat(softly.errorsCollected().get(7)).hasMessageContaining("NOT 10ms");
236251
}
237252

253+
@Test
254+
void should_pass_custom_description_to_chained_assertions_performed_after_CompletableFuture_succeedsWithin() {
255+
// GIVEN
256+
CompletableFuture<String> completableFuture = completedFuture("done");
257+
// WHEN
258+
softly.assertThat(completableFuture)
259+
.as("custom 20ms")
260+
.succeedsWithin(20, MILLISECONDS)
261+
.isEqualTo("not done 20ms")
262+
.isEqualTo("not ok 20ms");
263+
softly.assertThat(completableFuture)
264+
.as("custom 20ms asString")
265+
.succeedsWithin(20, MILLISECONDS, as(STRING))
266+
.contains("not 20ms")
267+
.containsIgnoringCase("NOT 20ms");
268+
softly.assertThat(completableFuture)
269+
.as("custom 10ms")
270+
.succeedsWithin(TEN_MILLIS)
271+
.isEqualTo("not done 10ms")
272+
.isEqualTo("not ok 10ms");
273+
softly.assertThat(completableFuture)
274+
.as("custom 10ms asString")
275+
.succeedsWithin(TEN_MILLIS, as(STRING))
276+
.contains("not 10ms")
277+
.containsIgnoringCase("NOT 10ms");
278+
// THEN
279+
assertThat(softly.errorsCollected()).hasSize(8);
280+
assertThat(softly.errorsCollected().get(0)).hasMessageStartingWith("[custom 20ms]").hasMessageContaining("not done 20ms");
281+
assertThat(softly.errorsCollected().get(1)).hasMessageStartingWith("[custom 20ms]").hasMessageContaining("not ok 20ms");
282+
assertThat(softly.errorsCollected().get(2)).hasMessageStartingWith("[custom 20ms asString]").hasMessageContaining("not 20ms");
283+
assertThat(softly.errorsCollected().get(3)).hasMessageStartingWith("[custom 20ms asString]").hasMessageContaining("NOT 20ms");
284+
assertThat(softly.errorsCollected().get(4)).hasMessageStartingWith("[custom 10ms]").hasMessageContaining("not done 10ms");
285+
assertThat(softly.errorsCollected().get(5)).hasMessageStartingWith("[custom 10ms]").hasMessageContaining("not ok 10ms");
286+
assertThat(softly.errorsCollected().get(6)).hasMessageStartingWith("[custom 10ms asString]").hasMessageContaining("not 10ms");
287+
assertThat(softly.errorsCollected().get(7)).hasMessageStartingWith("[custom 10ms asString]").hasMessageContaining("NOT 10ms");
288+
}
289+
290+
@Test
291+
void should_pass_custom_overridingErrorMessage_to_chained_assertions_performed_after_CompletableFuture_succeedsWithin() {
292+
// GIVEN
293+
CompletableFuture<String> completableFuture = completedFuture("done");
294+
// WHEN
295+
softly.assertThat(completableFuture)
296+
.overridingErrorMessage("custom 20ms")
297+
.succeedsWithin(20, MILLISECONDS)
298+
.isEqualTo("not done 20ms")
299+
.isEqualTo("not ok 20ms");
300+
softly.assertThat(completableFuture)
301+
.overridingErrorMessage("custom 20ms asString")
302+
.succeedsWithin(20, MILLISECONDS, as(STRING))
303+
.contains("not 20ms")
304+
.containsIgnoringCase("NOT 20ms");
305+
softly.assertThat(completableFuture)
306+
.overridingErrorMessage("custom 10ms")
307+
.succeedsWithin(TEN_MILLIS)
308+
.isEqualTo("not done 10ms")
309+
.isEqualTo("not ok 10ms");
310+
softly.assertThat(completableFuture)
311+
.overridingErrorMessage("custom 10ms asString")
312+
.succeedsWithin(TEN_MILLIS, as(STRING))
313+
.contains("not 10ms")
314+
.containsIgnoringCase("NOT 10ms");
315+
// THEN
316+
assertThat(softly.errorsCollected()).hasSize(8);
317+
assertThat(softly.errorsCollected().get(0)).hasMessage("custom 20ms");
318+
assertThat(softly.errorsCollected().get(1)).hasMessage("custom 20ms");
319+
assertThat(softly.errorsCollected().get(2)).hasMessage("custom 20ms asString");
320+
assertThat(softly.errorsCollected().get(3)).hasMessage("custom 20ms asString");
321+
assertThat(softly.errorsCollected().get(4)).hasMessage("custom 10ms");
322+
assertThat(softly.errorsCollected().get(5)).hasMessage("custom 10ms");
323+
assertThat(softly.errorsCollected().get(6)).hasMessage("custom 10ms asString");
324+
assertThat(softly.errorsCollected().get(7)).hasMessage("custom 10ms asString");
325+
}
326+
327+
@Test
328+
void should_collect_error_from_chained_assertions_after_CompletableFuture_isCompletedWithValueMatchingWithin() {
329+
// GIVEN
330+
CompletableFuture<String> future = completedFuture("done");
331+
// WHEN
332+
softly.assertThat(future)
333+
.isCompletedWithValueMatchingWithin(Predicate.isEqual("test"), TEN_MILLIS)
334+
.isEqualTo("not done 20ms");
335+
// THEN
336+
assertThat(softly.errorsCollected()).hasSize(2);
337+
assertThat(softly.errorsCollected().get(0)).hasMessageContaining("to match given predicate");
338+
assertThat(softly.errorsCollected().get(1)).hasMessageContaining("not done 20ms");
339+
}
340+
238341
@Test
239342
void should_only_collect_error_from_chained_assertions_performed_after_Future_succeedsWithin() {
240343
// GIVEN

assertj-tests/assertj-integration-tests/assertj-core-tests/src/test/java/org/assertj/tests/core/api/future/CompletableFutureAssert_isCompletedWithValueMatchingWithin_Test.java

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,8 +77,39 @@ void should_fail_on_pending_future_that_will_not_complete_in_provided_period() {
7777
void should_fail_on_pending_future_that_will_complete_in_provided_period_but_with_wrong_value() {
7878
// GIVEN
7979
CompletableFuture<String> future = completedFutureAfter("string", TEN_MS, executorService);
80-
// WHEN/THEN
81-
expectAssertionError(() -> assertThat(future).isCompletedWithValueMatchingWithin(s -> s.length() == 5, FIFTY_MS));
80+
// WHEN
81+
var assertionError = expectAssertionError(() -> assertThat(future).isCompletedWithValueMatchingWithin(s -> s.length() == 5,
82+
FIFTY_MS));
83+
// THEN
84+
then(assertionError).hasMessageContaining("to match given predicate");
85+
}
86+
87+
@Test
88+
void should_fail_with_custom_description() {
89+
// GIVEN
90+
CompletableFuture<String> future = completedFutureAfter("string", TEN_MS, executorService);
91+
// WHEN
92+
var assertionError = expectAssertionError(() -> assertThat(future)
93+
.as("Custom description")
94+
.isCompletedWithValueMatchingWithin(s -> s.length() == 5,
95+
FIFTY_MS));
96+
// THEN
97+
then(assertionError)
98+
.hasMessageContaining("[Custom description]")
99+
.hasMessageContaining("to match given predicate");
100+
}
101+
102+
@Test
103+
void should_fail_with_overridingErrorMessage() {
104+
// GIVEN
105+
CompletableFuture<String> future = completedFutureAfter("string", TEN_MS, executorService);
106+
// WHEN
107+
var assertionError = expectAssertionError(() -> assertThat(future)
108+
.overridingErrorMessage("Custom error message")
109+
.isCompletedWithValueMatchingWithin(s -> s.length() == 5,
110+
FIFTY_MS));
111+
// THEN
112+
then(assertionError).hasMessage("Custom error message");
82113
}
83114

84115
@Test

assertj-tests/assertj-integration-tests/assertj-core-tests/src/test/java/org/assertj/tests/core/api/future/CompletableFutureAssert_succeedsWithin_Test.java

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,9 @@
2525
import static org.assertj.core.util.FailureMessages.actualIsNull;
2626
import static org.assertj.tests.core.util.AssertionsUtil.expectAssertionError;
2727

28+
import java.time.Duration;
2829
import java.util.concurrent.CompletableFuture;
30+
import java.util.concurrent.TimeUnit;
2931

3032
import org.junit.jupiter.api.Test;
3133

@@ -112,4 +114,30 @@ void should_fail_if_completable_future_is_completed_exceptionally() {
112114
then(assertionError).hasMessageStartingWith("%nExpecting%n <CompletableFuture[Failed with the following stack trace:%njava.lang.RuntimeException: boom%%s%%n".formatted())
113115
.hasMessageContaining("to be completed within 1L Millis.");
114116
}
117+
118+
@Test
119+
void should_pass_custom_description() {
120+
// GIVEN
121+
String value = "done";
122+
CompletableFuture<String> future = completedFuture(value);
123+
// WHEN
124+
var assertionError = expectAssertionError(() -> assertThat(future).as("Custom description")
125+
.succeedsWithin(1, TimeUnit.MILLISECONDS, as(STRING))
126+
.startsWith("can"));
127+
// THEN
128+
then(assertionError).hasMessageStartingWith("[Custom description]");
129+
}
130+
131+
@Test
132+
void should_pass_overridingErrorMessage() {
133+
// GIVEN
134+
String value = "done";
135+
CompletableFuture<String> future = completedFuture(value);
136+
// WHEN
137+
var assertionError = expectAssertionError(() -> assertThat(future).overridingErrorMessage("Custom error")
138+
.succeedsWithin(1, TimeUnit.MILLISECONDS, as(STRING))
139+
.startsWith("can"));
140+
// THEN
141+
then(assertionError).hasMessage("Custom error");
142+
}
115143
}

assertj-tests/assertj-integration-tests/assertj-core-tests/src/test/java/org/assertj/tests/core/api/future/CompletableFutureAssert_succeedsWithin_duration_Test.java

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,4 +116,30 @@ void should_fail_if_completable_future_is_completed_exceptionally() {
116116
.hasMessageContaining("to be completed within 0.001s.");
117117
}
118118

119+
@Test
120+
void should_pass_custom_description() {
121+
// GIVEN
122+
String value = "done";
123+
CompletableFuture<String> future = completedFuture(value);
124+
// WHEN
125+
var assertionError = expectAssertionError(() -> assertThat(future).as("Custom description")
126+
.succeedsWithin(Duration.ofMillis(1), as(STRING))
127+
.startsWith("can"));
128+
// THEN
129+
then(assertionError).hasMessageStartingWith("[Custom description]");
130+
}
131+
132+
@Test
133+
void should_pass_overridingErrorMessage() {
134+
// GIVEN
135+
String value = "done";
136+
CompletableFuture<String> future = completedFuture(value);
137+
// WHEN
138+
var assertionError = expectAssertionError(() -> assertThat(future).overridingErrorMessage("Custom error")
139+
.succeedsWithin(Duration.ofMillis(1), as(STRING))
140+
.startsWith("can"));
141+
// THEN
142+
then(assertionError).hasMessage("Custom error");
143+
}
144+
119145
}

assertj-tests/assertj-integration-tests/assertj-core-tests/src/test/java/org/assertj/tests/core/api/future/FutureAssert_succeedsWithin_Test.java

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,10 @@
2525
import static org.assertj.core.util.FailureMessages.actualIsNull;
2626
import static org.assertj.tests.core.util.AssertionsUtil.expectAssertionError;
2727

28+
import java.time.Duration;
2829
import java.util.concurrent.CompletableFuture;
2930
import java.util.concurrent.Future;
31+
import java.util.concurrent.TimeUnit;
3032

3133
import org.junit.jupiter.api.Test;
3234

@@ -100,4 +102,30 @@ void should_fail_when_future_is_null() {
100102
// THEN
101103
then(assertionError).hasMessage(actualIsNull());
102104
}
105+
106+
@Test
107+
void should_pass_custom_description() {
108+
// GIVEN
109+
String value = "done";
110+
Future<String> future = completedFuture(value);
111+
// WHEN
112+
var assertionError = expectAssertionError(() -> assertThat(future).as("Custom description")
113+
.succeedsWithin(1, TimeUnit.MILLISECONDS, as(STRING))
114+
.startsWith("can"));
115+
// THEN
116+
then(assertionError).hasMessageStartingWith("[Custom description]");
117+
}
118+
119+
@Test
120+
void should_pass_overridingErrorMessage() {
121+
// GIVEN
122+
String value = "done";
123+
Future<String> future = completedFuture(value);
124+
// WHEN
125+
var assertionError = expectAssertionError(() -> assertThat(future).overridingErrorMessage("Custom error")
126+
.succeedsWithin(1, TimeUnit.MILLISECONDS, as(STRING))
127+
.startsWith("can"));
128+
// THEN
129+
then(assertionError).hasMessage("Custom error");
130+
}
103131
}

assertj-tests/assertj-integration-tests/assertj-core-tests/src/test/java/org/assertj/tests/core/api/future/FutureAssert_succeedsWithin_duration_Test.java

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,32 @@ void should_fail_when_future_is_null() {
104104
then(assertionError).hasMessage(actualIsNull());
105105
}
106106

107+
@Test
108+
void should_pass_custom_description() {
109+
// GIVEN
110+
String value = "done";
111+
Future<String> future = completedFuture(value);
112+
// WHEN
113+
var assertionError = expectAssertionError(() -> assertThat(future).as("Custom description")
114+
.succeedsWithin(Duration.ofMillis(1), as(STRING))
115+
.startsWith("can"));
116+
// THEN
117+
then(assertionError).hasMessageStartingWith("[Custom description]");
118+
}
119+
120+
@Test
121+
void should_pass_overridingErrorMessage() {
122+
// GIVEN
123+
String value = "done";
124+
Future<String> future = completedFuture(value);
125+
// WHEN
126+
var assertionError = expectAssertionError(() -> assertThat(future).overridingErrorMessage("Custom error")
127+
.succeedsWithin(Duration.ofMillis(1), as(STRING))
128+
.startsWith("can"));
129+
// THEN
130+
then(assertionError).hasMessage("Custom error");
131+
}
132+
107133
private static <U> Future<U> futureAfter(U value, long sleepDuration, ExecutorService service) {
108134
return service.submit(() -> {
109135
Thread.sleep(sleepDuration);

0 commit comments

Comments
 (0)