RxJava2 with JUnit: no exceptions thrown in tests

2.2k views Asked by At

The code below won't crash when running in JUnit environment. But it crashes when running in the app. I can see error logs in the console, but tests are marked as passed.

  @Test
  public void test() {
    Observable observable = Observable.error(new RuntimeException());
    observable.subscribe();
  }

So, the question is: how to make it crash in JUnit. Because yeah, if something doesn't work in the app it's a good thing if it doesn't work in the unit tests also :)

And in this example I have direct access to the observable. But in my real tests I don't have that. Real observables are just internal details of classes that being tested. The most thing I can to do is to inject schedulers or something.

So, how to make it crash without having direct access to the observable?

Also, I've just checked this code doesn't crash either:

  @Test
  public void test() {
    Observable observable = Observable.error(new RuntimeException());
    observable.subscribe(new Consumer() {
      @Override
      public void accept(Object o) throws Exception {
        throw new RuntimeException();
      }
    }, new Consumer<Throwable>() {
      @Override
      public void accept(Throwable throwable) throws Exception {
        throw new RuntimeException();
      }
    });
  }
3

There are 3 answers

0
bat_a_toe On BEST ANSWER

A small trick I use to tackle this problem, is creating a JUnit4 TestRule class that setups a custom RxJava error handler so it can throw when an unhandled RxJava error occurs:


/**
 * A rule that detects RxJava silent errors and reports them to JUnit
   (by throwing them).
 */
public class RxErrorRule implements TestRule {

    @Override
    public Statement apply(Statement base, Description description) {
        return new Statement() {
            @Override
            public void evaluate() throws Throwable {

                Consumer<? super Throwable> previous = RxJavaPlugins.getErrorHandler();

                AtomicReference<Throwable> result = setupErrorHandler();

                try {
                    base.evaluate();
                } finally {
                    RxJavaPlugins.setErrorHandler(previous);
                }

                if (result.get() != null) {
                    throw result.get();
                }
            }
        };
    }

    private AtomicReference<Throwable> setupErrorHandler() {
        AtomicReference<Throwable> result = new AtomicReference<>();

        RxJavaPlugins.setErrorHandler(new Consumer<Throwable>() {
            @Override
            public void accept(Throwable throwable) {
                result.set(throwable);
            }
        });
        return result;
    }

}

And in the unit test:


public class YourRxTest {

    @Rule
    public RxErrorRule errorRule = new RxErrorRule();

    // ...
}
0
Dmitry Ryadnenko On

According to akarnokd this is RxJava2 specific problem.

"Such usages no longer throw synchronously in 2.x but end up in the plugin handler for errors."

It is possible to check if any errors was thrown with this code

public static List<Throwable> trackPluginErrors() {
    final List<Throwable> list = Collections.synchronizedList(new ArrayList<Throwable>());

    RxJavaPlugins.setErrorHandler(new Consumer<Throwable>() {
        @Override
        public void accept(Throwable t) {
            list.add(t);
        }
    });

    return list;
}
11
Alexander Perfilyev On

Use TestSubscriber

    Observable observable = Observable.error(new RuntimeException());
    TestSubscriber testSubscriber = TestSubscriber.create();

    observable.subscribe(testSubscriber);

    testSubscriber.assertTerminalEvent();
    testSubscriber.assertError(RuntimeException.class);