rx-java Unit Testing TestScheduler


Example

TestSchedulers allows you to control time and execution of Observables instead of having to do busy waits, joining threads or anything to manipulate system time. This is VERY important if you want to write unit tests that are predictable, consistent and fast. Because you are manipulating time, there is no longer the chance that a thread got starved, that your test fails on a slower machine or that you waste execution time busy waiting for a result.

TestSchedulers can be provided via the overload that takes a Scheduler for any RxJava operations.

TestScheduler testScheduler = new TestScheduler();
TestSubscriber<Integer> subscriber = TestSubscriber.create();
Observable.just(1,2,3)
          .delay(10, TimeUnit.SECONDS, testScheduler)
          .subscribe(subscriber);

try {
    Thread.sleep(TimeUnit.SECONDS.toMillis(11));
} catch (InterruptedException ignored) { }
subscriber.assertValues(1,2,3); // fails

testScheduler.advanceTimeBy(10, TimeUnit.SECONDS);
subscriber.assertValues(1,2,3); // success

The TestScheduler is pretty basic. It only consists of three methods.

testScheduler.advanceTimeBy(amount, timeUnit);
testScheduler.advanceTimeTo(when, timeUnit);
testScheduler.triggerActions();

This lets you manipulate when the TestScheduler should fire all the actions pertaining to some time in the future.

While passing the scheduler works, this is not how the TestScheduler is commonly used because of how ineffective it is. Passing schedulers into classes ends up providing a lot of extra code for little gain. Instead, you can hook into RxJava's Schedulers.io()/computation()/etc. This is done with RxJava's Hooks. This lets you define what gets returned from a call from one of the Schedulers methods.

public final class TestSchedulers {

    public static TestScheduler test() {
        final TestScheduler testScheduler = new TestScheduler();
        RxJavaHooks.reset();
        RxJavaHooks.setOnComputationScheduler((scheduler) -> {
            return testScheduler;
        });
        RxJavaHooks.setOnIOScheduler((scheduler) -> {
            return testScheduler;
        });
        RxJavaHooks.setOnNewThreadScheduler((scheduler) -> {
            return testScheduler;
        });
        return testScheduler;
    }
}

This class allows the user to get the test scheduler that will be hooked up for all calls to Schedulers. A unit test would just need to get this scheduler in its setup. It is highly recommend aquiring it in the setup and not as any plain old field because your TestScheduler might try to triggerActions in from another unit test when you advance time. Now our example above becomes

TestScheduler testScheduler = new TestScheduler();
TestSubscriber<Integer> subscriber = TestSubscriber.create();
Observable.just(1,2,3)
          .delay(10, TimeUnit.SECONDS, testScheduler)
          .subscribe(subscriber);
testScheduler.advanceTimeBy(9, TimeUnit.SECONDS);
subscriber.assertValues(); // success (delay hasn't finished)
testScheduler.advanceTimeBy(10, TimeUnit.SECONDS);
subscriber.assertValues(1,2,3); // success (delay has finished)

That's how you can effectively remove the system clock from your unit test (at least as far as RxJava is concerned 😆)