javarx-javareactive-programmingrx-java2

RxJava - Conditional merging operations and asserting exception


I'm having one method which performs multiple conditional validations. I'd merge all validation into stream and assert if any exception occured.

public Observable<Void> performValidation(SomeObject obj) {
        Observable<Void> result = Observable.just(null);

        if (obj.getId() != null) {
            result.mergeWith(Observable.error(new RuntimeException("Validation 1 Fails")));
        }

        if (obj.getName() != null) {
            result.mergeWith(Observable.error(new IllegalArgumentException("Validation 2 Fails")));
        }

        return result;
    }

Later I peform assertion (let's assume that in someObj id is not null and name is null):

performValidation(someObj)
                .test()
                .awaitTerminalEvent()
                .assertNoValues()
                .assertError(RuntimeException.class)

And code fails with this exception:
java.lang.AssertionError: No errors (1 completion)

It looks like first validation, didn't merge with result object. What did I missed?


Solution

  • When you call an Observable operator, it returns a new Observable - it does not mutate the original. So your code would need to look like this:

    public Observable<Void> performValidation(SomeObject obj) {
            Observable<Void> result = Observable.just(null);
    
            if (obj.getId() != null) {
                result = result.mergeWith(Observable.error(new RuntimeException("Validation 1 Fails")));
            }
    
            if (obj.getName() != null) {
                result = result.mergeWith(Observable.error(new IllegalArgumentException("Validation 2 Fails")));
            }
    
            return result;
        }
    

    However, I also think this code would not work, because returning an Observable with a null value in it isn't actually supported; calling Observable.just(null) will throw a NullPointerException in RxJava2+. Here's the actual implementation in RxJava2:

    public static <T> Observable<T> just(T item) {
        ObjectHelper.requireNonNull(item, "item is null");
        return RxJavaPlugins.onAssembly(new ObservableJust(item));
    }
    

    You'd need to use a dummy value instead, or even better, have this method return Completable. Completable is intended to be used in cases where there is no value to be emitted from an Rx chain, meaning it simply succeeds or fails. Also note that this method doesn't really need to use RxJava at all, it can just be plain Java and throw an Exception directly. You would just call it in a doOnNext operator in your Observable chain.

    Your code is also not styled as idiomatic RxJava - it is an anti-pattern to assign an Observable to a local variable repeatedly the way you do. This is closer to the "proper" way, if you were to implement this method to return a Completable:

    public Completable performValidation(SomeObject obj) {
        return Observable.just(obj)
            .flatMapCompletable(obj -> {
                if (obj.getId() != null) {
                    return Completable.error(new RuntimeException("Validation 1 Fails")));
                }
    
                if (obj.getName() != null) {
                    return Completable.error(new IllegalArgumentException("Validation 2 Fails")));
                }
                return Completable.complete();
            });
    }