diff --git a/src/main/java/rx/observers/TestObserver.java b/src/main/java/rx/observers/TestObserver.java index 928956980b..45d89dae7c 100644 --- a/src/main/java/rx/observers/TestObserver.java +++ b/src/main/java/rx/observers/TestObserver.java @@ -15,12 +15,11 @@ */ package rx.observers; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; +import java.util.*; import rx.Notification; import rx.Observer; +import rx.exceptions.CompositeException; /** * Observer usable for unit testing to perform assertions, inspect received events or wrap a mocked Observer. @@ -113,11 +112,12 @@ public List getEvents() { */ public void assertReceivedOnNext(List items) { if (onNextEvents.size() != items.size()) { - throw new AssertionError("Number of items does not match. Provided: " + items.size() + " Actual: " + onNextEvents.size() + assertionError("Number of items does not match. Provided: " + items.size() + " Actual: " + onNextEvents.size() + ".\n" + "Provided values: " + items + "\n" - + "Actual values: " + onNextEvents); + + "Actual values: " + onNextEvents + + "\n"); } for (int i = 0; i < items.size(); i++) { @@ -126,12 +126,12 @@ public void assertReceivedOnNext(List items) { if (expected == null) { // check for null equality if (actual != null) { - throw new AssertionError("Value at index: " + i + " expected to be [null] but was: [" + actual + "]"); + assertionError("Value at index: " + i + " expected to be [null] but was: [" + actual + "]\n"); } } else if (!expected.equals(actual)) { - throw new AssertionError("Value at index: " + i + assertionError("Value at index: " + i + " expected to be [" + expected + "] (" + expected.getClass().getSimpleName() - + ") but was: [" + actual + "] (" + (actual != null ? actual.getClass().getSimpleName() : "null") + ")"); + + ") but was: [" + actual + "] (" + (actual != null ? actual.getClass().getSimpleName() : "null") + ")\n"); } } @@ -146,22 +146,64 @@ public void assertReceivedOnNext(List items) { */ public void assertTerminalEvent() { if (onErrorEvents.size() > 1) { - throw new AssertionError("Too many onError events: " + onErrorEvents.size()); + assertionError("Too many onError events: " + onErrorEvents.size()); } if (onCompletedEvents.size() > 1) { - throw new AssertionError("Too many onCompleted events: " + onCompletedEvents.size()); + assertionError("Too many onCompleted events: " + onCompletedEvents.size()); } if (onCompletedEvents.size() == 1 && onErrorEvents.size() == 1) { - throw new AssertionError("Received both an onError and onCompleted. Should be one or the other."); + assertionError("Received both an onError and onCompleted. Should be one or the other."); } if (onCompletedEvents.size() == 0 && onErrorEvents.size() == 0) { - throw new AssertionError("No terminal events received."); + assertionError("No terminal events received."); } } + /** + * Combines an assertion error message with the current completion and error state of this + * TestSubscriber, giving more information when some assertXXX check fails. + * @param message the message to use for the error + */ + final void assertionError(String message) { + StringBuilder b = new StringBuilder(message.length() + 32); + + b.append(message); + + + b.append(" ("); + int c = onCompletedEvents.size(); + b.append(c); + b.append(" completion"); + if (c != 1) { + b.append("s"); + } + b.append(")"); + + if (!onErrorEvents.isEmpty()) { + int size = onErrorEvents.size(); + b.append(" (+") + .append(size) + .append(" error"); + if (size != 1) { + b.append("s"); + } + b.append(")"); + } + + AssertionError ae = new AssertionError(b.toString()); + if (!onErrorEvents.isEmpty()) { + if (onErrorEvents.size() == 1) { + ae.initCause(onErrorEvents.get(0)); + } else { + ae.initCause(new CompositeException(onErrorEvents)); + } + } + throw ae; + } + // do nothing ... including swallowing errors private static Observer INERT = new Observer() { diff --git a/src/main/java/rx/observers/TestSubscriber.java b/src/main/java/rx/observers/TestSubscriber.java index 887bd2998b..5c64b3a8f8 100644 --- a/src/main/java/rx/observers/TestSubscriber.java +++ b/src/main/java/rx/observers/TestSubscriber.java @@ -292,7 +292,7 @@ public void assertTerminalEvent() { */ public void assertUnsubscribed() { if (!isUnsubscribed()) { - throw new AssertionError("Not unsubscribed."); + testObserver.assertionError("Not unsubscribed."); } } @@ -393,10 +393,10 @@ public Thread getLastSeenThread() { public void assertCompleted() { int s = testObserver.getOnCompletedEvents().size(); if (s == 0) { - throw new AssertionError("Not completed!"); + testObserver.assertionError("Not completed!"); } else if (s > 1) { - throw new AssertionError("Completed multiple times: " + s); + testObserver.assertionError("Completed multiple times: " + s); } } @@ -409,10 +409,10 @@ public void assertCompleted() { public void assertNotCompleted() { int s = testObserver.getOnCompletedEvents().size(); if (s == 1) { - throw new AssertionError("Completed!"); + testObserver.assertionError("Completed!"); } else if (s > 1) { - throw new AssertionError("Completed multiple times: " + s); + testObserver.assertionError("Completed multiple times: " + s); } } @@ -427,7 +427,7 @@ public void assertNotCompleted() { public void assertError(Class clazz) { List err = testObserver.getOnErrorEvents(); if (err.size() == 0) { - throw new AssertionError("No errors"); + testObserver.assertionError("No errors"); } else if (err.size() > 1) { AssertionError ae = new AssertionError("Multiple errors: " + err.size()); @@ -452,7 +452,7 @@ public void assertError(Class clazz) { public void assertError(Throwable throwable) { List err = testObserver.getOnErrorEvents(); if (err.size() == 0) { - throw new AssertionError("No errors"); + testObserver.assertionError("No errors"); } else if (err.size() > 1) { AssertionError ae = new AssertionError("Multiple errors: " + err.size()); @@ -477,7 +477,7 @@ public void assertNoTerminalEvent() { int s = testObserver.getOnCompletedEvents().size(); if (err.size() > 0 || s > 0) { if (err.isEmpty()) { - throw new AssertionError("Found " + err.size() + " errors and " + s + " completion events instead of none"); + testObserver.assertionError("Found " + err.size() + " errors and " + s + " completion events instead of none"); } else if (err.size() == 1) { AssertionError ae = new AssertionError("Found " + err.size() + " errors and " + s + " completion events instead of none"); @@ -500,7 +500,7 @@ public void assertNoTerminalEvent() { public void assertNoValues() { int s = testObserver.getOnNextEvents().size(); if (s > 0) { - throw new AssertionError("No onNext events expected yet some received: " + s); + testObserver.assertionError("No onNext events expected yet some received: " + s); } } @@ -514,7 +514,7 @@ public void assertNoValues() { public void assertValueCount(int count) { int s = testObserver.getOnNextEvents().size(); if (s != count) { - throw new AssertionError("Number of onNext events differ; expected: " + count + ", actual: " + s); + testObserver.assertionError("Number of onNext events differ; expected: " + count + ", actual: " + s); } } diff --git a/src/test/java/rx/observers/TestSubscriberTest.java b/src/test/java/rx/observers/TestSubscriberTest.java index 1974a81a2f..9ff09edd20 100644 --- a/src/test/java/rx/observers/TestSubscriberTest.java +++ b/src/test/java/rx/observers/TestSubscriberTest.java @@ -18,7 +18,7 @@ import static org.junit.Assert.*; import static org.mockito.Mockito.*; -import java.util.Arrays; +import java.util.*; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; @@ -26,8 +26,10 @@ import org.junit.rules.ExpectedException; import org.mockito.InOrder; -import rx.*; +import rx.Observable; +import rx.Observer; import rx.Scheduler.Worker; +import rx.Subscriber; import rx.exceptions.*; import rx.functions.Action0; import rx.schedulers.Schedulers; @@ -610,9 +612,105 @@ public void assertValuesShouldThrowIfNumberOfItemsDoesNotMatch() { } catch (AssertionError expected) { assertEquals("Number of items does not match. Provided: 2 Actual: 3.\n" + "Provided values: [1, 2]\n" + - "Actual values: [a, b, c]", + "Actual values: [a, b, c]\n (0 completions)", expected.getMessage() ); } } + + @Test + public void assertionFailureGivesActiveDetails() { + TestSubscriber ts = new TestSubscriber(); + + ts.onNext("a"); + ts.onNext("b"); + ts.onNext("c"); + ts.onError(new TestException("forced failure")); + + try { + ts.assertValues("1", "2"); + fail(); + } catch (AssertionError expected) { + assertEquals("Number of items does not match. Provided: 2 Actual: 3.\n" + + "Provided values: [1, 2]\n" + + "Actual values: [a, b, c]\n (0 completions) (+1 error)", + expected.getMessage() + ); + Throwable ex = expected.getCause(); + assertEquals(TestException.class, ex.getClass()); + assertEquals("forced failure", ex.getMessage()); + } + } + + @Test + public void assertionFailureShowsMultipleErrors() { + TestSubscriber ts = new TestSubscriber(); + + ts.onNext("a"); + ts.onNext("b"); + ts.onNext("c"); + ts.onError(new TestException("forced failure")); + ts.onError(new TestException("forced failure 2")); + + try { + ts.assertValues("1", "2"); + fail(); + } catch (AssertionError expected) { + assertEquals("Number of items does not match. Provided: 2 Actual: 3.\n" + + "Provided values: [1, 2]\n" + + "Actual values: [a, b, c]\n (0 completions) (+2 errors)", + expected.getMessage() + ); + Throwable ex = expected.getCause(); + assertEquals(CompositeException.class, ex.getClass()); + List list = ((CompositeException)ex).getExceptions(); + assertEquals(2, list.size()); + assertEquals("forced failure", list.get(0).getMessage()); + assertEquals("forced failure 2", list.get(1).getMessage()); + } + } + + @Test + public void assertionFailureShowsCompletion() { + TestSubscriber ts = new TestSubscriber(); + + ts.onNext("a"); + ts.onNext("b"); + ts.onNext("c"); + ts.onCompleted(); + + try { + ts.assertValues("1", "2"); + fail(); + } catch (AssertionError expected) { + assertEquals("Number of items does not match. Provided: 2 Actual: 3.\n" + + "Provided values: [1, 2]\n" + + "Actual values: [a, b, c]\n (1 completion)", + expected.getMessage() + ); + } + } + + @Test + public void assertionFailureShowsMultipleCompletions() { + TestSubscriber ts = new TestSubscriber(); + + ts.onNext("a"); + ts.onNext("b"); + ts.onNext("c"); + ts.onCompleted(); + ts.onCompleted(); + + try { + ts.assertValues("1", "2"); + fail(); + } catch (AssertionError expected) { + assertEquals("Number of items does not match. Provided: 2 Actual: 3.\n" + + "Provided values: [1, 2]\n" + + "Actual values: [a, b, c]\n (2 completions)", + expected.getMessage() + ); + } + } + }