An alternative to RxAndroidPlugins and RxJavaPlugins: Scheduler Injection
In my previous article, I described how the RxAndroidPlugins APIs for RxJava 2 can be used to override Schedulers to create a suitable execution environment for JVM-based unit tests.
As I hinted, that’s not your only option. In this follow-up, I will describe another possibility; Scheduler Injection and we’ll see how it compares to using the plugins approach.
If you are familiar with RxJava 1.x, but haven’t yet started with RxJava 2.x; don’t worry, I’ll mention some specifics from 2.x, but the principles described are still generally applicable to both versions.
Scheduler Injection?
Scheduler Injection is simply supplying the desired Scheduler
instance to your operators, rather than letting them run on their defaults.
It is most succinctly performed by employing the Strategy Pattern. You start by declaring an interface; SchedulerProvider
which defines access to all the Schedulers that your app uses.
public interface SchedulerProvider {
Scheduler ui();
Scheduler computation();
Scheduler io();
Scheduler special();
// Other schedulers as required…
}
In your production code, you then implement this interface so that it returns the corresponding Scheduler instances from RxAndroid, RxJava or your own custom Scheduler. It would look something like this:
final class AppSchedulerProvider implements SchedulerProvider {
@Override
public Scheduler ui() {
return AndroidSchedulers.mainThread();
} @Override
public Scheduler computation() {
return Schedulers.computation();
} @Override
public Scheduler io() {
return Schedulers.io();
} @Override
public Scheduler special() {
return MyOwnSchedulers.special();
}
}
Now wherever a Scheduler is used, you supply that class with the SchedulerProvider
instance (via constructor or dependency injection framework) and reference the corresponding Scheduler in your operators. For example, say we originally have the sequence:
bookstoreModel.getFavoriteBook()
.map(Book::getTitle)
.delay(5, TimeUnit.SECONDS)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(view::setBookTitle));
This becomes -
bookstoreModel.getFavoriteBook()
.map(Book::getTitle)
.delay(5, TimeUnit.SECONDS,
this.schedulerProvider.computation())
.observeOn(this.schedulerProvider.ui())
.subscribe(view::setBookTitle));
Importantly, all explicit and implicit use of Schedulers must be replaced by their equivalent from the SchedulerProvider
. Naturally this includes those referenced in operators such as observeOn
and subscribeOn
, but also in operators like delay
and timeout
which operate on a particular Scheduler unless otherwise specified.
@SchedulerSupport
In RxJava 2, you can tell if an operator implicitly uses a particular Scheduler by inspecting the @SchedulerSupport
annotation on its declaration. This annotation has no inherent run-time effect, but rather it serves as documentation of the Scheduler used by the operator.
If we look at the delay
operator in RxJava 2’s Observable
class we can see that its @SchedulerSupport
annotation has the value SchedulerSupport.COMPUTATION
, meaning that it uses Schedulers.computation()
internally -
These types of operators will also have an overloaded equivalent, annotated with the value SchedulerSupport.CUSTOM
, which allow you to manually specify the Scheduler to be used -
You just need to change your operator method invocation to the overloaded variant and pass in the appropriate Scheduler. Pretty simple!
Operators that don’t use a Scheduler, such as map
, have the annotation value SchedulerSupport.NONE
. Their invocations won’t require any changes.
In Your Tests
Now as we know from my previous article, we can’t use the AppSchedulerProvider
implementation in our JVM-based unit tests due to the Android dependencies and asynchronous scheduling; we need a different implementation.
The exact implementation depends on the particular requirements of your test. That’s where the Scheduler Injection pattern shines; you can override the Schedulers in whatever way you like and modify them whenever it suits you.
In our example, we want to verify the influence of the delay
operator, so we would use a SchedulerProvider
backed by an instance of RxJava’s TestScheduler
.
This is quite a straightforward task; it’s just another implementation of SchedulerProvider
injected into the class under test. See the example project for the details.
Verbose but Flexible
When compared to using the Plugins, you can see that the additional constructor dependency and operator parameter could add a bit of unwanted verbosity and wiring ceremony to your code.
Another potential concern is that you might occasionally forget to supply the additional parameter to the operator, leading to unpredictable test results and wasted time when debugging.
On the upside, using the SchedulerProvider
gives you complete flexibility and control over your Schedulers. You won’t need to be concerned with the quirks of the Plugin APIs, nor will you need to get your head around the Plugin API changes from RxJava 1.x. Furthermore, if you are using your own custom Scheduler, then it’s simple to add that into your SchedulerProvider
and override it when necessary.
All this means that with the SchedulerProvider
, you have a single place where you can freely manage your Schedulers.
Decision Time
So what approach should be used? Should you use the Plugins or use Scheduler Injection?
My recommendation for app developers is to use Scheduler Injection.
I’ve used it in large production apps and in my experience the flexibility that you gain outweighs the issue of verbosity. With a little bit of repetition and awareness, using it becomes second nature. I haven’t found myself forgetting to supply a Scheduler; any reasonably comprehensive set of tests would make that painfully obvious to you.
The Plugins might be less intrusive to your production code, but when it comes time to test, their limited flexibility and somewhat quirky initialization sequence eliminates those gains.
So when would you want to use RxJavaPlugins or RxAndroidPlugins?
Well, they remain helpful for library developers who might not want to expose Scheduler configuration to clients, but want control when testing. Also, they could be an option if you’ve inherited a legacy code-base which somehow makes extensive use of RxJava, but not of dependency injection. Hopefully that’s not your situation!
So, if you can, go down the Scheduler Injection path and write your own SchedulerProvider
.
Thanks for reading!
See the full example source code here.