Testing Rx code

Problem

In our app we strongly rely on Reactive Programming approach. Almost all async calls are handled by RxJava (the only exception is database which still works very well with old-fashioned Cursor, LoaderManager and Content Provider set). With a great possibilities given by reactive extension there is one issue — call chains, especially those which are passed between different threads by observeOn() and subscribeOn() operators, make testing problematic. In more complex solutions with IO or Computation schedulers simple using toBlocking() call doesn’t work — test is literally blocked during method execution.

Based on our experience we’ll try to figure out not only the solution for that problem, but also the reason of described behaviour.

Source code of all described cases is available here.

Solution

Imagine a simple application with one activity and one manager. First manager’s method produces some data, second one transforms data from first one, and third one also adds some decoration to transformation. Activity takes transformed and decorated data from manager and prints it on screen. All actions in managers are done asynchronously.

How can we test managers correctness?

First of all, we create test class with test case for manager’s getTransformedNumbers() method. Class looks like this:

When we try to run such tests, exception occurres.

It’s because manager methods use AndroidSchedulers.mainThread() scheduler, and Looper.mainLooper() is not mocked in tests. Since we probably would use Robolectric in other test cases, Test class could be annotated with RobolectricTestRunner — it’ll mock required functionallity for us.

However, it doesn’t help a lot. Tests compile now, could be executed, but output shows, that none of the observables was called. Why is that? Because at least one observable in chain is executed on thread other than Main. Tests end, but it seems like no action was performed. Actually observable was fired, but since test doesn’t wait for results from threads other than Main and ends as soon as last instruction done on Main is done, we couldn’t notice that.

What’s solution for current problem? Mocking schedulers. Here is a good article explaining how to do that. To make long story short, method is to provide our own Rx schedulers implementation. We do it by putting own RxJavaTestPlugins class into package rx.plugins. Class should define schedulers hook, which returns Schedulers.immediate() when any kind of scheduler is requested. Also there should be methods for registering and removing our schedulers hook implementation.

It’s also required to redefine AndroidSchedulers hook, by providing RxAndroidTestPlugin class in package rx.android.plugins:

Then, in our test class we have to use setup methods from mocked plugins to apply mock schedulers. It’s also a very good idea to clean up after tests are over, to be sure, that other tests are unaffected.

Now test could be run. Voila! After running tests we’ve got desired output:

So far, so good. But what if we want test some more complex cases? Manager’s getTransformedNumberMoreFlatMapped() method decorates observables with additional chain action. Here we have flatMap operator which adds another word to received one.

We could write a test for that using observer from first test case:

When we run it, test hangs. Why? Because of ImmediateScheduler usage of Thread.sleep(), which causes dead lock here.

How can we handle it? Change Schedulers.immediate() to something else probably would do the work. And what’s the best candidate to replace it and why? AndroidSchedulers.mainThread() — it’ll make us sure, that whole Rx code is done on single thread. So we provide alternative method in RxJavaTestPlugins, and call it from test class setUp() method. Also, RxAndroidTestPlugins class could now be safely removed from the project — whole action would be executed with AndroidSchedulers.mainThred().

Setup methods for tests slighty changes:

Now every test will work like we expect.

Alternative solution

There is at least one alternative way to solve this problem.

Instead of mocking schedulers we can use Transformers. All managers using subscribeOn(), observeOn() should not use them directly, but rather make use of composite() method, and Observable.Transformer (more info about it here) . Transformers repository could be inserted as a dependency, and mocked in tests. Our manager and its test would now looks like this:

What are the advantages of this solution? We could remove mocking schedulers, or mock it with something else than AndroidSchedulers.mainThead() — e.g. TestScheduler, which gives us some powerful methods to test observables behaviours.

Source Code

Full source code of described example is available on GitHub repository.