RxJava: 2.x: Uncaught errors fail silently in junit tests.

I was writing a utility that involved decorating observers and catching uncaught errors from the delegate observer’s onError handling when I noticed in testing that tests always passed. There would be log output, but tests would pass none the less.

Demo of the issue can be found here, but the gist of it is that the following test passes in standard JUnit:

@Test
public void blerg() {
    Observable.error(new RuntimeException("This should fail the test"))
            .subscribe();
}

I’m trying to understand why errors are passed directly to the handler rather than just thrown, and also curious to get thoughts on making this behavior configurable (if for no other reason than to disable in junit tests). I can’t find any details about why this behavior happens in JUnit either.

I think this has worrisome implications. In our codebase alone, I dropped in an error watching rule to track these and discovered over 150 tests with uncaught exceptions that are being marked as passing otherwise.

About this issue

  • Original URL
  • State: closed
  • Created 7 years ago
  • Reactions: 13
  • Comments: 36 (29 by maintainers)

Commits related to this issue

Most upvoted comments

I think I’m not being clear. I’m not trying to test if onXXX methods throw. I’m concerned that errors in the stream with no error handling don’t actually fail tests because they’re sent directly to the handler rather than just throwing if there’s no plugin error handler. Trying to understand why we send directly to the handler.

Write a custom Subscriber that throws from its onError method and subscribe with that instead of the empty subscribe().

We normally don’t have errors in streams and convert them to values using Kotlin sealed classes, so we don’t use subscribe() overloads with error handling, rxlint won’t help (and it’s Android specific).

But we need to detect unexpected errors in tests.

I believe RxJava already offers the necessary hooking capabilities to detect such errors in tests

And we have 3k+ test cases written with different test frameworks, hooking into each test class is manual and error-prone work. There are hundreds if not thousands other projects migrating from v1 to v2 that’ll have to do the same and this number will only grow.

Unlike 1.x, OnErrorNotImplementedException is not thrown. Also please read on the error handling section of the wiki.

Okay, let’s see the proposed code changes.

This is actually a problem outside of tests too unfortunately. Say I’m on Android and I want to write a custom plugin for decorating observers (such as tracking analytics). I want to give the delegate observer a shot at onError and try/catch to react to how it handled it, but if it’s the default rxjava execution and hands it over to the current thread’s uncaught exception handler via its internal uncaught() method, the app will just quite immediately without the catch clause getting a chance.

Installing a custom error handler via plugins is not an option either, as it is just try/catch’d and pipes the error again through the above uncaught() pipeline.

Could I propose making this configurable via system property? Something like rx2.uncaughtBehavior with values "default" and "rethrow"?

Edit: Proposed a strawman impl in #5569

throwing from Reactive-Streams is somewhat of a gamble

That’s what I want to avoid and throw only if RxJava v2 specific subscriber was used.

OnErrorNotImplementedException can not be thrown in the middle of rx chain. Only by calling v2-specificsubscribe() without error handler, which reduces possibility of multi-library problems since they will use RS org.reactivestreams.Publisher.subscribe(org.reactivestreams.Subscriber).

As said before, v2 violates some RS rules if v2 specific subscriber was used, but respects RS if RS subscriber comes. And I can create problematic multi-library flow because of that, but only if I’d like to.


For example, a multi-library flow may not consider it as fatal or swallow it completely anyway

Btw, this is already happening with RxJava v1 and v2 connected through interop library, v2 swallows OnErrorNotImplemented thrown by v1:

@Test
fun interopProblem() {
    try {
        Observable2
                .fromCallable { throw RuntimeException("Test") }
                .toObservable1()
                .subscribe()
        fail("Should throw rx.exceptions.OnErrorNotImplementedException")
    } catch (expected: rx.exceptions.OnErrorNotImplementedException) {
        // ok.
    }
}

most likely you have to mock out schedulers already, making the test synchronous and at the end, you can synchronously check any excess errors with an approach like I showed above.

Good point, but.

Scheduling is explicit, our code will not compile if we won’t pass scheduler (either real or test one).

While current design of error handling is implicit and not only allows tests to compile without checks suggested above, but also returns normally and tests pass as false positives.

2.x Subscribers and Observers must not throw from their onXXX methods. Install an error handler before the test and verify there was no undeliverable error. See how we do it in RxJava tests:

https://github.com/ReactiveX/RxJava/blob/3bfc275ca2f8d14a67305bbadb7bbc6315205250/src/test/java/io/reactivex/parallel/ParallelPeekTest.java#L55