Skip to content

1.x: TestSubscriber extra info on assertion failures #3934

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
May 23, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 54 additions & 12 deletions src/main/java/rx/observers/TestObserver.java
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -113,11 +112,12 @@ public List<Object> getEvents() {
*/
public void assertReceivedOnNext(List<T> 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++) {
Expand All @@ -126,12 +126,12 @@ public void assertReceivedOnNext(List<T> 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");

}
}
Expand All @@ -146,22 +146,64 @@ public void assertReceivedOnNext(List<T> 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) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TestObserver needs tests too

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

assertValues go to testObserver.assertReceivedOnNext()

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure… but it's implementation detail…

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<Object> INERT = new Observer<Object>() {

Expand Down
20 changes: 10 additions & 10 deletions src/main/java/rx/observers/TestSubscriber.java
Original file line number Diff line number Diff line change
Expand Up @@ -292,7 +292,7 @@ public void assertTerminalEvent() {
*/
public void assertUnsubscribed() {
if (!isUnsubscribed()) {
throw new AssertionError("Not unsubscribed.");
testObserver.assertionError("Not unsubscribed.");
}
}

Expand Down Expand Up @@ -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);
}
}

Expand All @@ -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);
}
}

Expand All @@ -427,7 +427,7 @@ public void assertNotCompleted() {
public void assertError(Class<? extends Throwable> clazz) {
List<Throwable> 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());
Expand All @@ -452,7 +452,7 @@ public void assertError(Class<? extends Throwable> clazz) {
public void assertError(Throwable throwable) {
List<Throwable> 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());
Expand All @@ -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");
Expand All @@ -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);
}
}

Expand All @@ -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);
}
}

Expand Down
104 changes: 101 additions & 3 deletions src/test/java/rx/observers/TestSubscriberTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,18 @@
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;

import org.junit.*;
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;
Expand Down Expand Up @@ -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<String> ts = new TestSubscriber<String>();

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<String> ts = new TestSubscriber<String>();

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<Throwable> 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<String> ts = new TestSubscriber<String>();

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<String> ts = new TestSubscriber<String>();

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()
);
}
}

}