rx-java

Unit Testing

Remarks#

Because all the Schedulers methods are static, unit tests utilizing the RxJava hooks cannot be ran in parallel on the same JVM instance. If they where, one TestScheduler would be removed in the middle of a unit test. That is basically the downside of using the Schedulers class.

TestSubscriber

TestSubscribers allow you to avoid the work creating your own Subscriber or subscribe Action<?> to verify that certain values where delivered, how many there are, if the Observable completed, an exception was raised and a whole lot more.

Getting Started

This example just shows an assertion that the values 1,2,3 and 4 where passed into the Observable via onNext.

TestSubscriber<Integer> ts = TestSubscriber.create();
Observable.just(1,2,3,4).subscribe(ts);
ts.assertValues(1,2,3,4); // Success

assertValues asserts that the count is correct. If you were to only pass some of the values, the assert would fail.

TestSubscriber<Integer> ts = TestSubscriber.create();
Observable.just(1,2,3,4).subscribe(ts);
ts.assertValues(1,2,3); // Fail

assertValues uses the equals method when doing asserts. This lets you easily test classes that are treated as data.

TestSubscriber<Object> ts = TestSubscriber.create();
Observable.just(new Object(), new Object()).subscribe(ts);
ts.assertValues(new Object(), new Object()); // Fail

This example shows a class that has a equals defined and asserting the values from the Observable.

public class Room {

    public String floor;
    public String number;

    @Override
    public boolean equals(Object o) {
        if (o == this) {
            return true;
        }
        if (o instanceof Room) {
            Room that = (Room) o;
            return (this.floor.equals(that.floor))
                    && (this.number.equals(that.number));
        }
        return false;
    }
}

TestSubscriber<Room> ts = TestSubscriber.create();
Observable.just(new Room("1", "10")).subscribe(ts);
ts.assertValue(new Room("1", "10"); // Success

Also take note that we use the shorter assertValue because we only need to check for one item.

Getting all events

If need be you can also ask for all the events as a list.

TestSubscriber<Integer> ts = TestSubscriber.create();
Observable.just(1,2,3,4).subscribe(ts);
List<Integer> onNextEvents = ts.getOnNextEvents();
List<Throwable> onErrorEvents = ts.getOnErrorEvents();
List<Notification<Integer>> onCompletedEvents = ts.getOnCompletedEvents();

Asserting on events

If you want to do more extensive tests on your events, you can combine getOnNextEvents (or getOn*Events) with your favorite assertion library:

Observable<Integer> obs = Observable.just(1,2,3,4)
    .filter( x -> x % 2 == 0);

// note that we instanciate TestSubscriber via the constructor here 
TestSubscriber<Integer> ts = new TestSubscriber();
obs.subscribe(ts);

// Note that we are not using Observable#forEach here
// but java.lang.Iterable#forEach.
// You should never use Observable#forEach unless you know
// exactly what you're doing
ts.getOnNextEvents()
    .forEach( integer -> assertTrue(integer % 2 == 0));

Testing Observable#error

You can make sure that the correct exception class is emitted:

Observable<Integer> obs = Observable.error(new Exception("I am a Teapot"));

TestSubscriber<Integer> ts = new TestSubscriber<>();
obs.subscribe(ts);

ts.assertError(Exception.class);

You can also make sure that the exact Exception was thrown:

Exception e = new Exception("I am a Teapot");
Observable<Integer> obs = Observable.error(e);

TestSubscriber<Integer> ts = new TestSubscriber<>();
obs.subscribe(ts);

ts.assertError(e);

TestScheduler

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 😆)


This modified text is an extract of the original Stack Overflow Documentation created by the contributors and released under CC BY-SA 3.0 This website is not affiliated with Stack Overflow