Skip to content

Commit ffe49c3

Browse files
jraskaakarnokd
authored andcommitted
2.x: Add error assertion with predicate to TestSubscriber and TestObserver (#4586)
* Add error assertion with predicate (#4498) * Add error assertion with predicate to TestObserver (#4498)
1 parent 24448b4 commit ffe49c3

File tree

4 files changed

+261
-2
lines changed

4 files changed

+261
-2
lines changed

src/main/java/io/reactivex/observers/TestObserver.java

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import io.reactivex.disposables.Disposable;
2222
import io.reactivex.exceptions.CompositeException;
2323
import io.reactivex.functions.Consumer;
24+
import io.reactivex.functions.Predicate;
2425
import io.reactivex.internal.disposables.DisposableHelper;
2526
import io.reactivex.internal.functions.ObjectHelper;
2627
import io.reactivex.internal.fuseable.QueueDisposable;
@@ -424,10 +425,12 @@ public final TestObserver<T> assertNoErrors() {
424425
*
425426
* <p>The comparison is performed via Objects.equals(); since most exceptions don't
426427
* implement equals(), this assertion may fail. Use the {@link #assertError(Class)}
427-
* overload to test against the class of an error instead of an instance of an error.
428+
* overload to test against the class of an error instead of an instance of an error
429+
* or {@link #assertError(Predicate)} to test with different condition.
428430
* @param error the error to check
429431
* @return this;
430432
* @see #assertError(Class)
433+
* @see #assertError(Predicate)
431434
*/
432435
public final TestObserver<T> assertError(Throwable error) {
433436
int s = errors.size();
@@ -475,6 +478,43 @@ public final TestObserver<T> assertError(Class<? extends Throwable> errorClass)
475478
return this;
476479
}
477480

481+
/**
482+
* Asserts that this TestObserver received exactly one onError event for which
483+
* the provided predicate returns true.
484+
* @param errorPredicate
485+
* the predicate that receives the error Throwable
486+
* and should return true for expected errors.
487+
* @return this
488+
*/
489+
public final TestObserver<T> assertError(Predicate<Throwable> errorPredicate) {
490+
int s = errors.size();
491+
if (s == 0) {
492+
throw fail("No errors");
493+
}
494+
495+
boolean found = false;
496+
497+
for (Throwable e : errors) {
498+
try {
499+
if (errorPredicate.test(e)) {
500+
found = true;
501+
break;
502+
}
503+
} catch (Exception ex) {
504+
throw ExceptionHelper.wrapOrThrow(ex);
505+
}
506+
}
507+
508+
if (found) {
509+
if (s != 1) {
510+
throw fail("Error present but other errors as well");
511+
}
512+
} else {
513+
throw fail("Error not present");
514+
}
515+
return this;
516+
}
517+
478518
/**
479519
* Assert that this TestObserver received exactly one onNext value which is equal to
480520
* the given value with respect to Objects.equals.
@@ -825,6 +865,7 @@ public final TestObserver<T> assertOf(Consumer<? super TestObserver<T>> check) {
825865
* @param values the expected values, asserted in order
826866
* @return this
827867
* @see #assertFailure(Class, Object...)
868+
* @see #assertFailure(Predicate, Object...)
828869
* @see #assertFailureAndMessage(Class, String, Object...)
829870
*/
830871
public final TestObserver<T> assertResult(T... values) {
@@ -848,6 +889,22 @@ public final TestObserver<T> assertFailure(Class<? extends Throwable> error, T..
848889
.assertNotComplete();
849890
}
850891

892+
/**
893+
* Assert that the upstream signalled the specified values in order and then failed
894+
* with a Throwable for which the provided predicate returns true.
895+
* @param errorPredicate
896+
* the predicate that receives the error Throwable
897+
* and should return true for expected errors.
898+
* @param values the expected values, asserted in order
899+
* @return this
900+
*/
901+
public final TestObserver<T> assertFailure(Predicate<Throwable> errorPredicate, T... values) {
902+
return assertSubscribed()
903+
.assertValues(values)
904+
.assertError(errorPredicate)
905+
.assertNotComplete();
906+
}
907+
851908
/**
852909
* Assert that the upstream signalled the specified values in order,
853910
* then failed with a specific class or subclass of Throwable

src/main/java/io/reactivex/subscribers/TestSubscriber.java

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import io.reactivex.disposables.Disposable;
2323
import io.reactivex.exceptions.CompositeException;
2424
import io.reactivex.functions.Consumer;
25+
import io.reactivex.functions.Predicate;
2526
import io.reactivex.internal.functions.ObjectHelper;
2627
import io.reactivex.internal.fuseable.QueueSubscription;
2728
import io.reactivex.internal.subscriptions.SubscriptionHelper;
@@ -468,10 +469,12 @@ public final TestSubscriber<T> assertNoErrors() {
468469
*
469470
* <p>The comparison is performed via Objects.equals(); since most exceptions don't
470471
* implement equals(), this assertion may fail. Use the {@link #assertError(Class)}
471-
* overload to test against the class of an error instead of an instance of an error.
472+
* overload to test against the class of an error instead of an instance of an error
473+
* or {@link #assertError(Predicate)} to test with different condition.
472474
* @param error the error to check
473475
* @return this
474476
* @see #assertError(Class)
477+
* @see #assertError(Predicate)
475478
*/
476479
public final TestSubscriber<T> assertError(Throwable error) {
477480
int s = errors.size();
@@ -519,6 +522,43 @@ public final TestSubscriber<T> assertError(Class<? extends Throwable> errorClass
519522
return this;
520523
}
521524

525+
/**
526+
* Asserts that this TestSubscriber received exactly one onError event for which
527+
* the provided predicate returns true.
528+
* @param errorPredicate
529+
* the predicate that receives the error Throwable
530+
* and should return true for expected errors.
531+
* @return this
532+
*/
533+
public final TestSubscriber<T> assertError(Predicate<Throwable> errorPredicate) {
534+
int s = errors.size();
535+
if (s == 0) {
536+
throw fail("No errors");
537+
}
538+
539+
boolean found = false;
540+
541+
for (Throwable e : errors) {
542+
try {
543+
if (errorPredicate.test(e)) {
544+
found = true;
545+
break;
546+
}
547+
} catch (Exception ex) {
548+
throw ExceptionHelper.wrapOrThrow(ex);
549+
}
550+
}
551+
552+
if (found) {
553+
if (s != 1) {
554+
throw fail("Error present but other errors as well");
555+
}
556+
} else {
557+
throw fail("Error not present");
558+
}
559+
return this;
560+
}
561+
522562
/**
523563
* Assert that this TestSubscriber received exactly one onNext value which is equal to
524564
* the given value with respect to Objects.equals.
@@ -869,6 +909,7 @@ public final TestSubscriber<T> assertOf(Consumer<? super TestSubscriber<T>> chec
869909
* @param values the expected values, asserted in order
870910
* @return this
871911
* @see #assertFailure(Class, Object...)
912+
* @see #assertFailure(Predicate, Object...)
872913
* @see #assertFailureAndMessage(Class, String, Object...)
873914
*/
874915
public final TestSubscriber<T> assertResult(T... values) {
@@ -892,6 +933,22 @@ public final TestSubscriber<T> assertFailure(Class<? extends Throwable> error, T
892933
.assertNotComplete();
893934
}
894935

936+
/**
937+
* Assert that the upstream signalled the specified values in order and then failed
938+
* with a Throwable for which the provided predicate returns true.
939+
* @param errorPredicate
940+
* the predicate that receives the error Throwable
941+
* and should return true for expected errors.
942+
* @param values the expected values, asserted in order
943+
* @return this
944+
*/
945+
public final TestSubscriber<T> assertFailure(Predicate<Throwable> errorPredicate, T... values) {
946+
return assertSubscribed()
947+
.assertValues(values)
948+
.assertError(errorPredicate)
949+
.assertNotComplete();
950+
}
951+
895952
/**
896953
* Assert that the upstream signalled the specified values in order,
897954
* then failed with a specific class or subclass of Throwable

src/test/java/io/reactivex/observers/TestObserverTest.java

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import io.reactivex.disposables.*;
3232
import io.reactivex.exceptions.TestException;
3333
import io.reactivex.functions.*;
34+
import io.reactivex.internal.functions.Functions;
3435
import io.reactivex.internal.fuseable.QueueDisposable;
3536
import io.reactivex.internal.operators.observable.ObservableScalarXMap.ScalarDisposable;
3637
import io.reactivex.internal.subscriptions.EmptySubscription;
@@ -332,6 +333,13 @@ public void assertError() {
332333
// expected
333334
}
334335

336+
try {
337+
ts.assertError(Functions.<Throwable>alwaysTrue());
338+
throw new RuntimeException("Should have thrown");
339+
} catch (AssertionError ex) {
340+
// expected
341+
}
342+
335343
try {
336344
ts.assertErrorMessage("");
337345
throw new RuntimeException("Should have thrown");
@@ -367,6 +375,15 @@ public void assertError() {
367375

368376
ts.assertError(TestException.class);
369377

378+
ts.assertError(Functions.<Throwable>alwaysTrue());
379+
380+
ts.assertError(new Predicate<Throwable>() {
381+
@Override
382+
public boolean test(Throwable t) throws Exception {
383+
return t.getMessage() != null && t.getMessage().contains("Forced");
384+
}
385+
});
386+
370387
ts.assertErrorMessage("Forced failure");
371388

372389
try {
@@ -390,6 +407,13 @@ public void assertError() {
390407
// expected
391408
}
392409

410+
try {
411+
ts.assertError(Functions.<Throwable>alwaysFalse());
412+
throw new RuntimeException("Should have thrown");
413+
} catch (AssertionError exc) {
414+
// expected
415+
}
416+
393417
try {
394418
ts.assertNoErrors();
395419
throw new RuntimeException("Should have thrown");
@@ -428,12 +452,16 @@ public void assertFailure() {
428452

429453
ts.assertFailure(TestException.class);
430454

455+
ts.assertFailure(Functions.<Throwable>alwaysTrue());
456+
431457
ts.assertFailureAndMessage(TestException.class, "Forced failure");
432458

433459
ts.onNext(1);
434460

435461
ts.assertFailure(TestException.class, 1);
436462

463+
ts.assertFailure(Functions.<Throwable>alwaysTrue(), 1);
464+
437465
ts.assertFailureAndMessage(TestException.class, "Forced failure", 1);
438466
}
439467

@@ -965,6 +993,12 @@ public void assertErrorMultiple() {
965993
} catch (AssertionError ex) {
966994
// expected
967995
}
996+
try {
997+
ts.assertError(Functions.<Throwable>alwaysTrue());
998+
throw new RuntimeException("Should have thrown!");
999+
} catch (AssertionError ex) {
1000+
// expected
1001+
}
9681002
try {
9691003
ts.assertErrorMessage("");
9701004
throw new RuntimeException("Should have thrown!");
@@ -973,6 +1007,24 @@ public void assertErrorMultiple() {
9731007
}
9741008
}
9751009

1010+
@Test
1011+
public void testErrorInPredicate() {
1012+
TestObserver<Object> ts = new TestObserver<Object>();
1013+
ts.onError(new RuntimeException());
1014+
try {
1015+
ts.assertError(new Predicate<Throwable>() {
1016+
@Override
1017+
public boolean test(Throwable throwable) throws Exception {
1018+
throw new TestException();
1019+
}
1020+
});
1021+
} catch (TestException ex) {
1022+
// expected
1023+
return;
1024+
}
1025+
fail("Error in predicate but not thrown!");
1026+
}
1027+
9761028
@Test
9771029
public void assertComplete() {
9781030
TestObserver<Integer> ts = new TestObserver<Integer>();

0 commit comments

Comments
 (0)