/** * Copyright 2014 Netflix, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package rx.util; import rx.Notification; import rx.Observable; import rx.functions.Func1; import rx.functions.Func2; public final class AssertObservable { private AssertObservable() { throw new IllegalStateException("No instances!"); } /** * Asserts that two Observables are equal. If they are not, an {@link AssertionError} is thrown * with the given message. If <code>expecteds</code> and <code>actuals</code> are * <code>null</code>, they are considered equal. * * @param expected * Observable with expected values. * @param actual * Observable with actual values */ public static <T> void assertObservableEqualsBlocking(Observable<T> expected, Observable<T> actual) { assertObservableEqualsBlocking(null, expected, actual); } /** * Asserts that two Observables are equal. If they are not, an {@link AssertionError} is thrown * with the given message. If <code>expected</code> and <code>actual</code> are * <code>null</code>, they are considered equal. * * @param message * the identifying message for the {@link AssertionError} (<code>null</code> okay) * @param expected * Observable with expected values. * @param actual * Observable with actual values */ public static <T> void assertObservableEqualsBlocking(String message, Observable<T> expected, Observable<T> actual) { assertObservableEquals(expected, actual).toBlocking().lastOrDefault(null); } /** * Asserts that two {@link Observable}s are equal and returns an empty {@link Observable}. If * they are not, an {@link Observable} is returned that calls onError with an {@link AssertionError} when subscribed to. If <code>expected</code> and <code>actual</code> * are <code>null</code>, they are considered equal. * * @param message * the identifying message for the {@link AssertionError} (<code>null</code> okay) * @param expected * Observable with expected values. * @param actual * Observable with actual values */ public static <T> Observable<Void> assertObservableEquals(Observable<T> expected, Observable<T> actual) { return assertObservableEquals(null, expected, actual); } /** * Asserts that two {@link Observable}s are equal and returns an empty {@link Observable}. If * they are not, an {@link Observable} is returned that calls onError with an {@link AssertionError} when subscribed to with the given message. If <code>expected</code> * and <code>actual</code> are <code>null</code>, they are considered equal. * * @param message * the identifying message for the {@link AssertionError} (<code>null</code> okay) * @param expected * Observable with expected values. * @param actual * Observable with actual values */ public static <T> Observable<Void> assertObservableEquals(final String message, Observable<T> expected, Observable<T> actual) { if (actual == null && expected != null) { return Observable.error(new AssertionError((message != null ? message + ": " : "") + "Actual was null and expected was not")); } if (actual != null && expected == null) { return Observable.error(new AssertionError((message != null ? message + ": " : "") + "Expected was null and actual was not")); } if (actual == null && expected == null) { return Observable.empty(); } Func2<? super Notification<T>, ? super Notification<T>, Notification<String>> zipFunction = new Func2<Notification<T>, Notification<T>, Notification<String>>() { @Override public Notification<String> call(Notification<T> expectedNotfication, Notification<T> actualNotification) { if (expectedNotfication.equals(actualNotification)) { StringBuilder message = new StringBuilder(); message.append(expectedNotfication.getKind()); if (expectedNotfication.hasValue()) message.append(" ").append(expectedNotfication.getValue()); if (expectedNotfication.hasThrowable()) message.append(" ").append(expectedNotfication.getThrowable()); return Notification.createOnNext("equals " + message.toString()); } else { StringBuilder error = new StringBuilder(); error.append("expected:<").append(expectedNotfication.getKind()); if (expectedNotfication.hasValue()) error.append(" ").append(expectedNotfication.getValue()); if (expectedNotfication.hasThrowable()) error.append(" ").append(expectedNotfication.getThrowable()); error.append("> but was:<").append(actualNotification.getKind()); if (actualNotification.hasValue()) error.append(" ").append(actualNotification.getValue()); if (actualNotification.hasThrowable()) error.append(" ").append(actualNotification.getThrowable()); error.append(">"); return Notification.createOnError(new AssertionError(error.toString())); } } }; Func2<Notification<String>, Notification<String>, Notification<String>> accumulator = new Func2<Notification<String>, Notification<String>, Notification<String>>() { @Override public Notification<String> call(Notification<String> a, Notification<String> b) { String message = a.isOnError() ? a.getThrowable().getMessage() : a.getValue(); boolean fail = a.isOnError(); message += "\n\t" + (b.isOnError() ? b.getThrowable().getMessage() : b.getValue()); fail |= b.isOnError(); if (fail) return Notification.createOnError(new AssertionError(message)); else return Notification.createOnNext(message); } }; Observable<Void> outcomeObservable = Observable.zip(expected.materialize(), actual.materialize(), zipFunction).reduce(accumulator).map(new Func1<Notification<String>, Notification<Void>>() { @Override public Notification<Void> call(Notification<String> outcome) { if (outcome.isOnError()) { String fullMessage = (message != null ? message + ": " : "") + "Observables are different\n\t" + outcome.getThrowable().getMessage(); return Notification.createOnError(new AssertionError(fullMessage)); } return Notification.createOnCompleted(); } }).dematerialize(); return outcomeObservable; } }