RxJava — “Seriously great” — Me 2015

RxJava: Unit Testing Examples

After my initial foray, I wondered about unit testing RxJava. So here are a couple of examples.

Setting the Scene

Lets say you have queried an API and have a model which contains an array of country names. A requirement is that the UI does not show any name which is mythical, “Atlantis” for example.

Testing, How?

For unit testing our code we will want to mock out both the Observable and the Subscriber, to leave us with a nice isolated test for the filtering logic in the middle. Something like this:

To mock the subscriber side of this we can use a TestSubscriber. A test subscriber has methods on which can be used to inspect the events it receives and make assertions on them.

Mocking out the data is as simple as injecting the input data into the ViewModel via constructor injection. This will mean we have full control of the input data whilst not knowing the logic of the method which returns the Observable.

Using this setup we have the ideal setup for unit testing in which we control the inputs and verify the outputs with the ViewModel logic remaining a black box.

To give a better example of this setup a test might look something along the lines of this:

@Test
public void testGetCountriesObservable_FiltersOutAtlantis() throws Exception {
    List<String> inputCountries = new ArrayList<>();
inputCountries.add("USA");
inputCountries.add("China");
inputCountries.add("Atlantis");

List<String> expectedCountries = new ArrayList<>();
expectedCountries.add("USA");
expectedCountries.add("China");

ViewModel viewModel = new ViewModel(inputCountries);
TestSubscriber<String> testSubscriber = new TestSubscriber<>();

viewModel.getCountriesObservable().subscribe(testSubscriber);

testSubscriber.assertReceivedOnNext(expectedCountries);
}

As you can see we are setting up the values to inject as a List at the top of the test. This is then injected into the ViewModel to setup out test state. After this the ViewModel has it’s getCountriesObservable() called which we subscribe to using a TestSubscriber. After the subscribe we assert the events emitted match the expect events. Now if the logic inside of getCountriesObservable() is correct then this test should pass.

Perhaps we have obtained the countries from the network and need to modify a isRequesting flag to true a request is underway. The obvious way would be to add isRequesting = true to the top of the getCountriesObservable(). This however is probably wrong. Obtaining the Observable and subscribing to it are two different actions and as such we should wait for a subscribe before we change the flag. Luckily RxJava has just such a method, onSubcribe(). Add this to the chain of calls and you will end up with something like this:

return Observable.just(countries)
.doOnSubscribe(() -> isRequesting = true)
.flatMapIterable(countries -> countries)
.filter(country -> !country.equalsIgnoreCase("Atlantis"));

Now to test this we could write something like this:

@Test
public void getCountriesObservable_StartsRequestWhenSubscribedTo() throws Exception {

ViewModel viewModel = new ViewModel(null);
TestSubscriber<String> testSubscriber = new TestSubscriber<>();

viewModel.getCountriesObservable().subscribe(testSubscriber);

assertTrue(viewModel.isRequesting());
}

And say we now need to test that isRequesting is reset when the Observable either completes or errors. We can use onTerminate() for that.

return Observable.just(countries)
.doOnSubscribe(() -> isRequesting = true)
.doOnTerminate(() -> isRequesting = false)
.flatMapIterable(countries -> countries)
.filter(country -> !country.equalsIgnoreCase("Atlantis"));

Great, now we have a neat way of flipping the flags and testing our code.

Now the eagle eyed among you might have spotted a little problem which relates to concurrency and testing that first flag for indicating a request has started. By the time you get round to checking the onComplete has already been called and it has been set back to false meaning the test will fail. Now the only way I have found thus far is to create an Observable via Observable.never() I can’t help but think that there is an easier way so let me know if you find it.

These are just a few (very shallow) ways in which you can test RxJava, I would recommend researching further. RxJava really is a broad and wide tool and with great power yada yada you don’t need the hard sell, honestly it’s awesome I highly recommend checking it out.

I have uploaded a quick sample of this project which has a few example tests as outlined so feel free to take a look.