At Hootsuite, RxJava and RxAndroid are some of the core libraries we use when building apps for Android. They provide us with a powerful API for writing beautiful and functional asynchronous code. However, the asynchronicity that these libraries provide can also make it more difficult to write unit tests. Many common ways of testing asynchronous code like thread sleeps or using a CountDownLatch are inconsistent and messy, especially when applied to RxJava. In order to avoid these problems, we must look deeper into the RxJava API.
There are two main components to test independently in an asynchronous system: the process submission and the process execution. In RxJava this means that we must test both creation of Observables and the execution of Subscribers. Fortunately, RxJava (and RxAndroid) come with several tools to make this easier.
A very common way of testing Observables looks something like this:
These methods work because they allow us to make Observable output synchronous and read the data that is outputted. If we use the materialize() method on the Observable, it is even possible to see which Subscriber method the emitted objects would call. However, this approach limits Observable testability, since it makes it difficult and messy to test streams of data and makes it impossible to test how the Subscriber interacts with the Observable (e.g. using requestMore()) . Luckily, RxJava provides the TestSubscriber class to make testing simpler.
A TestSubscriber is a type of Subscriber that can be used to perform assertions, inspect received events, or wrap a mocked Subscriber. This allows you to test RxJava specific concepts such as Requests, Completion, and Unsubscribing in addition to making it easier to test Observable data.
Here is an example of it in action:
If you are testing an Observable that emits data at some rate (eg. a timer), then using a TestScheduler is useful. TestScheduler allows you to control time by advancing your clock at will. It can be used in tandem with TestSubscriber to provide even more control over execution.
Here is an example of one in action:
Note that we add the TestScheduler as an argument in the delay, because by default the delay method uses the ComputationScheduler and cannot be overwritten. There are a few ways to overcome this limitation: you can write your methods for getting Observables to have the Scheduler as an argument; or, if you don’t want to modify your code for writing tests, then you could also create a scheduler hook for getComputationScheduler.
Testing the process execution (the Subscription) is more difficult because it usually runs in a different thread than the unit test. This means that normally you can’t rely on your assertion being executed after your Subscription. However, RxJava provides a way to change execution such that Subscribers always run first. This is done by overwriting the global Schedulers for RxJava and RxAndroid with ones that cause immediate execution.
In order for your tests to run properly you must replace the Scheduler before RxJava initialization. If you are using Robolectric this is done by replacing your default schedulers with Schedulers.immediate() in your base test Application class. This needs to be done before super.onCreate() runs, or it will not be able replace the Schedulers since the test app has already been initialized.
Here is an example of how to do this:
In addition to replacing the IOScheduler we can also replace the ComputationScheduler with the TestScheduler (although, this may not work in RxJava 1.1). This would provide us greater control over the clock for delays and buffers, but would also break any code that relies on the ComputationScheduler.
Notice that we call RxJavaTestPlugins.resetPlugins() rather than RxJavaPlugins.getInstance().reset(). This is because RxJavaPlugins.reset() is a package private method which means that we must create our own method in a class that is inside a package called rx.plugins that accesses this method. We can do this in the same way as Federico Paolinelli’s post by extending the RxJavaPlugins class like so:
Once all these steps are complete all of your Subscriptions will now run synchronously immediately after they are created. Next you will need to create some Observables to test your Subscribers with. RxJava provides a number of ways of doing this:
- Observable.just() creates an Observable that you can use to emit a single test or mock Object for your Subscriber to subscribe to.
- Observable.error() can be used to emit an error to test how your Subscriber handles errors
- Observable.from() takes a list and emits it as a steam from Observables. This can be used to test how your Subscriber works with a stream from data.
There are many more ways to create Observables, these are just the ones we use most often in our tests. Just like with other unit tests it is often useful to use mock objects when writing unit tests for Subscribers.
While RxJava is an incredibly useful library, it can sometimes make writing unit tests more difficult. However, learning to write unit tests for RxJava forces developers to think about many of the complexities that RxJava abstracts away. With the methods mentioned in this post it should be possible to fully integrate RxJava into your existing unit testing framework.
Thanks to @simtse, @benhergert, @WesAlcock, and @DavidGraig for helping me write this post. Additional thanks go out to Federico Paolinelli, Alexis Mas, and Iván Carballo whose posts on the topic provided both content and inspiration for my own.
About the Author
Denis Trailin is Co-op Software Developer on the Android team who is currently studying Computer Science at UBC. In his spare time he builds robot sailboats and skis. Follow him on Twitter @DenisTrailin.