Skip to content

Commit 7fed2ed

Browse files
akarnokdzsxwing
authored andcommitted
1.x: TestSubscriber extra info on assertion failures (#3934)
This PR adds extra information to assertion failure messages on `TestSubscriber` and `TestObserver`, indicating: - the listener didn't receive any `onCompleted` calls, which is an indication of hung or skipping operation, - there were errors received, indicating a failure in the event generation process. Previously, if there was something wrong with the sequence, the order and type of assertions were mostly unhelpful: if `assertValues` was first, the lack of values failure could hide a revealing onError call. If the `assertNoErrors()` was first, the error is visible but no way of knowing how far the sequence got. Now, it is generally okay to use `assertValues` first, which along the difference, will print the lack of completion and the number of exceptions received, plus, the `AssertionError` will have its cause initialized to the actual or composited exception. The message format thus changes: ``` original assertion message with details (0 completions) (+1 error) ... caused by ... ``` This extra information saved me a lot of time in 2.x and Rsc development. Note that this change doesn't make the `assertXXX`s also assert for completion or error at all. If the values match, but there is an additional error instead of completion, one has to assert that separately, just like now.
1 parent 3ea9265 commit 7fed2ed

File tree

3 files changed

+165
-25
lines changed

3 files changed

+165
-25
lines changed

src/main/java/rx/observers/TestObserver.java

Lines changed: 54 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,11 @@
1515
*/
1616
package rx.observers;
1717

18-
import java.util.ArrayList;
19-
import java.util.Collections;
20-
import java.util.List;
18+
import java.util.*;
2119

2220
import rx.Notification;
2321
import rx.Observer;
22+
import rx.exceptions.CompositeException;
2423

2524
/**
2625
* Observer usable for unit testing to perform assertions, inspect received events or wrap a mocked Observer.
@@ -114,11 +113,12 @@ public List<Object> getEvents() {
114113
*/
115114
public void assertReceivedOnNext(List<T> items) {
116115
if (onNextEvents.size() != items.size()) {
117-
throw new AssertionError("Number of items does not match. Provided: " + items.size() + " Actual: " + onNextEvents.size()
116+
assertionError("Number of items does not match. Provided: " + items.size() + " Actual: " + onNextEvents.size()
118117
+ ".\n"
119118
+ "Provided values: " + items
120119
+ "\n"
121-
+ "Actual values: " + onNextEvents);
120+
+ "Actual values: " + onNextEvents
121+
+ "\n");
122122
}
123123

124124
for (int i = 0; i < items.size(); i++) {
@@ -127,12 +127,12 @@ public void assertReceivedOnNext(List<T> items) {
127127
if (expected == null) {
128128
// check for null equality
129129
if (actual != null) {
130-
throw new AssertionError("Value at index: " + i + " expected to be [null] but was: [" + actual + "]");
130+
assertionError("Value at index: " + i + " expected to be [null] but was: [" + actual + "]\n");
131131
}
132132
} else if (!expected.equals(actual)) {
133-
throw new AssertionError("Value at index: " + i
133+
assertionError("Value at index: " + i
134134
+ " expected to be [" + expected + "] (" + expected.getClass().getSimpleName()
135-
+ ") but was: [" + actual + "] (" + (actual != null ? actual.getClass().getSimpleName() : "null") + ")");
135+
+ ") but was: [" + actual + "] (" + (actual != null ? actual.getClass().getSimpleName() : "null") + ")\n");
136136

137137
}
138138
}
@@ -147,22 +147,64 @@ public void assertReceivedOnNext(List<T> items) {
147147
*/
148148
public void assertTerminalEvent() {
149149
if (onErrorEvents.size() > 1) {
150-
throw new AssertionError("Too many onError events: " + onErrorEvents.size());
150+
assertionError("Too many onError events: " + onErrorEvents.size());
151151
}
152152

153153
if (onCompletedEvents.size() > 1) {
154-
throw new AssertionError("Too many onCompleted events: " + onCompletedEvents.size());
154+
assertionError("Too many onCompleted events: " + onCompletedEvents.size());
155155
}
156156

157157
if (onCompletedEvents.size() == 1 && onErrorEvents.size() == 1) {
158-
throw new AssertionError("Received both an onError and onCompleted. Should be one or the other.");
158+
assertionError("Received both an onError and onCompleted. Should be one or the other.");
159159
}
160160

161161
if (onCompletedEvents.size() == 0 && onErrorEvents.size() == 0) {
162-
throw new AssertionError("No terminal events received.");
162+
assertionError("No terminal events received.");
163163
}
164164
}
165165

166+
/**
167+
* Combines an assertion error message with the current completion and error state of this
168+
* TestSubscriber, giving more information when some assertXXX check fails.
169+
* @param message the message to use for the error
170+
*/
171+
final void assertionError(String message) {
172+
StringBuilder b = new StringBuilder(message.length() + 32);
173+
174+
b.append(message);
175+
176+
177+
b.append(" (");
178+
int c = onCompletedEvents.size();
179+
b.append(c);
180+
b.append(" completion");
181+
if (c != 1) {
182+
b.append("s");
183+
}
184+
b.append(")");
185+
186+
if (!onErrorEvents.isEmpty()) {
187+
int size = onErrorEvents.size();
188+
b.append(" (+")
189+
.append(size)
190+
.append(" error");
191+
if (size != 1) {
192+
b.append("s");
193+
}
194+
b.append(")");
195+
}
196+
197+
AssertionError ae = new AssertionError(b.toString());
198+
if (!onErrorEvents.isEmpty()) {
199+
if (onErrorEvents.size() == 1) {
200+
ae.initCause(onErrorEvents.get(0));
201+
} else {
202+
ae.initCause(new CompositeException(onErrorEvents));
203+
}
204+
}
205+
throw ae;
206+
}
207+
166208
// do nothing ... including swallowing errors
167209
private static Observer<Object> INERT = new Observer<Object>() {
168210

src/main/java/rx/observers/TestSubscriber.java

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -292,7 +292,7 @@ public void assertTerminalEvent() {
292292
*/
293293
public void assertUnsubscribed() {
294294
if (!isUnsubscribed()) {
295-
throw new AssertionError("Not unsubscribed.");
295+
testObserver.assertionError("Not unsubscribed.");
296296
}
297297
}
298298

@@ -393,10 +393,10 @@ public Thread getLastSeenThread() {
393393
public void assertCompleted() {
394394
int s = testObserver.getOnCompletedEvents().size();
395395
if (s == 0) {
396-
throw new AssertionError("Not completed!");
396+
testObserver.assertionError("Not completed!");
397397
} else
398398
if (s > 1) {
399-
throw new AssertionError("Completed multiple times: " + s);
399+
testObserver.assertionError("Completed multiple times: " + s);
400400
}
401401
}
402402

@@ -409,10 +409,10 @@ public void assertCompleted() {
409409
public void assertNotCompleted() {
410410
int s = testObserver.getOnCompletedEvents().size();
411411
if (s == 1) {
412-
throw new AssertionError("Completed!");
412+
testObserver.assertionError("Completed!");
413413
} else
414414
if (s > 1) {
415-
throw new AssertionError("Completed multiple times: " + s);
415+
testObserver.assertionError("Completed multiple times: " + s);
416416
}
417417
}
418418

@@ -427,7 +427,7 @@ public void assertNotCompleted() {
427427
public void assertError(Class<? extends Throwable> clazz) {
428428
List<Throwable> err = testObserver.getOnErrorEvents();
429429
if (err.size() == 0) {
430-
throw new AssertionError("No errors");
430+
testObserver.assertionError("No errors");
431431
} else
432432
if (err.size() > 1) {
433433
AssertionError ae = new AssertionError("Multiple errors: " + err.size());
@@ -452,7 +452,7 @@ public void assertError(Class<? extends Throwable> clazz) {
452452
public void assertError(Throwable throwable) {
453453
List<Throwable> err = testObserver.getOnErrorEvents();
454454
if (err.size() == 0) {
455-
throw new AssertionError("No errors");
455+
testObserver.assertionError("No errors");
456456
} else
457457
if (err.size() > 1) {
458458
AssertionError ae = new AssertionError("Multiple errors: " + err.size());
@@ -477,7 +477,7 @@ public void assertNoTerminalEvent() {
477477
int s = testObserver.getOnCompletedEvents().size();
478478
if (err.size() > 0 || s > 0) {
479479
if (err.isEmpty()) {
480-
throw new AssertionError("Found " + err.size() + " errors and " + s + " completion events instead of none");
480+
testObserver.assertionError("Found " + err.size() + " errors and " + s + " completion events instead of none");
481481
} else
482482
if (err.size() == 1) {
483483
AssertionError ae = new AssertionError("Found " + err.size() + " errors and " + s + " completion events instead of none");
@@ -500,7 +500,7 @@ public void assertNoTerminalEvent() {
500500
public void assertNoValues() {
501501
int s = testObserver.getOnNextEvents().size();
502502
if (s > 0) {
503-
throw new AssertionError("No onNext events expected yet some received: " + s);
503+
testObserver.assertionError("No onNext events expected yet some received: " + s);
504504
}
505505
}
506506

@@ -514,7 +514,7 @@ public void assertNoValues() {
514514
public void assertValueCount(int count) {
515515
int s = testObserver.getOnNextEvents().size();
516516
if (s != count) {
517-
throw new AssertionError("Number of onNext events differ; expected: " + count + ", actual: " + s);
517+
testObserver.assertionError("Number of onNext events differ; expected: " + count + ", actual: " + s);
518518
}
519519
}
520520

src/test/java/rx/observers/TestSubscriberTest.java

Lines changed: 101 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,18 @@
1818
import static org.junit.Assert.*;
1919
import static org.mockito.Mockito.*;
2020

21-
import java.util.Arrays;
21+
import java.util.*;
2222
import java.util.concurrent.TimeUnit;
2323
import java.util.concurrent.atomic.AtomicBoolean;
2424

2525
import org.junit.*;
2626
import org.junit.rules.ExpectedException;
2727
import org.mockito.InOrder;
2828

29-
import rx.*;
29+
import rx.Observable;
30+
import rx.Observer;
3031
import rx.Scheduler.Worker;
32+
import rx.Subscriber;
3133
import rx.exceptions.*;
3234
import rx.functions.Action0;
3335
import rx.schedulers.Schedulers;
@@ -610,9 +612,105 @@ public void assertValuesShouldThrowIfNumberOfItemsDoesNotMatch() {
610612
} catch (AssertionError expected) {
611613
assertEquals("Number of items does not match. Provided: 2 Actual: 3.\n" +
612614
"Provided values: [1, 2]\n" +
613-
"Actual values: [a, b, c]",
615+
"Actual values: [a, b, c]\n (0 completions)",
614616
expected.getMessage()
615617
);
616618
}
617619
}
620+
621+
@Test
622+
public void assertionFailureGivesActiveDetails() {
623+
TestSubscriber<String> ts = new TestSubscriber<String>();
624+
625+
ts.onNext("a");
626+
ts.onNext("b");
627+
ts.onNext("c");
628+
ts.onError(new TestException("forced failure"));
629+
630+
try {
631+
ts.assertValues("1", "2");
632+
fail();
633+
} catch (AssertionError expected) {
634+
assertEquals("Number of items does not match. Provided: 2 Actual: 3.\n" +
635+
"Provided values: [1, 2]\n" +
636+
"Actual values: [a, b, c]\n (0 completions) (+1 error)",
637+
expected.getMessage()
638+
);
639+
Throwable ex = expected.getCause();
640+
assertEquals(TestException.class, ex.getClass());
641+
assertEquals("forced failure", ex.getMessage());
642+
}
643+
}
644+
645+
@Test
646+
public void assertionFailureShowsMultipleErrors() {
647+
TestSubscriber<String> ts = new TestSubscriber<String>();
648+
649+
ts.onNext("a");
650+
ts.onNext("b");
651+
ts.onNext("c");
652+
ts.onError(new TestException("forced failure"));
653+
ts.onError(new TestException("forced failure 2"));
654+
655+
try {
656+
ts.assertValues("1", "2");
657+
fail();
658+
} catch (AssertionError expected) {
659+
assertEquals("Number of items does not match. Provided: 2 Actual: 3.\n" +
660+
"Provided values: [1, 2]\n" +
661+
"Actual values: [a, b, c]\n (0 completions) (+2 errors)",
662+
expected.getMessage()
663+
);
664+
Throwable ex = expected.getCause();
665+
assertEquals(CompositeException.class, ex.getClass());
666+
List<Throwable> list = ((CompositeException)ex).getExceptions();
667+
assertEquals(2, list.size());
668+
assertEquals("forced failure", list.get(0).getMessage());
669+
assertEquals("forced failure 2", list.get(1).getMessage());
670+
}
671+
}
672+
673+
@Test
674+
public void assertionFailureShowsCompletion() {
675+
TestSubscriber<String> ts = new TestSubscriber<String>();
676+
677+
ts.onNext("a");
678+
ts.onNext("b");
679+
ts.onNext("c");
680+
ts.onCompleted();
681+
682+
try {
683+
ts.assertValues("1", "2");
684+
fail();
685+
} catch (AssertionError expected) {
686+
assertEquals("Number of items does not match. Provided: 2 Actual: 3.\n" +
687+
"Provided values: [1, 2]\n" +
688+
"Actual values: [a, b, c]\n (1 completion)",
689+
expected.getMessage()
690+
);
691+
}
692+
}
693+
694+
@Test
695+
public void assertionFailureShowsMultipleCompletions() {
696+
TestSubscriber<String> ts = new TestSubscriber<String>();
697+
698+
ts.onNext("a");
699+
ts.onNext("b");
700+
ts.onNext("c");
701+
ts.onCompleted();
702+
ts.onCompleted();
703+
704+
try {
705+
ts.assertValues("1", "2");
706+
fail();
707+
} catch (AssertionError expected) {
708+
assertEquals("Number of items does not match. Provided: 2 Actual: 3.\n" +
709+
"Provided values: [1, 2]\n" +
710+
"Actual values: [a, b, c]\n (2 completions)",
711+
expected.getMessage()
712+
);
713+
}
714+
}
715+
618716
}

0 commit comments

Comments
 (0)