Testing Asynchronous Code in Open Event Orga App using RxJava
In the last blog post, we saw how to test complex interactions through our apps using stubbed behaviors by Mockito. In this post, I’ll be talking about how to test RxJava components such as Observables. This one will focus on testing complex situations using RxJava as the library itself provides methods to unit test your reactive streams, so that you don’t have to go out of your way to set contraptions like callback captors, and implement your own interfaces as stubs of the original one. The test suite (kind of) provided by RxJava also allows you to test the fate of your stream, like confirming that they got subscribed or an error was thrown; or test an individual emitted item, like its value or with a predicate logic of your own, etc. We have used this heavily in Open Event Orga App (Github Repo) to detect if the app is correctly loading and refreshing resources from the correct source. We also capture certain triggers happening to events like saving of data on reloading so that the database remains in a consistent state. Here, we’ll look at some basic examples and move to some complex ones later. So, let’s start.
public class AttendeeRepositoryTest { private AttendeeRepository attendeeRepository; @Before public void setUp() { testDemo = new TestDemo(); } @Test public void shouldReturnAttendeeByName() { // TODO: Implement test } }
This is our basic test class setup with general JUnit settings. We’ll start by writing our tests, the first of which will be to test that we can get an attendee by name. The attendee class is a model class with firstName and lastName. And we will be checking if we get a valid attendee by passing a full name. Note that although we will be talking about the implementation of the code which we are writing tests for, but only in an abstract manner, meaning we won’t be dealing with production code, just the test.
So, as we know that Observables provide a stream of data. But here, we are only concerned with one attendee. Technically, we should be using Single, but for generality, we’ll stick with Observables.
So, a person from the background of JUnit would be tempted to write this code below.
Attendee attendee = attendeeRepository.getByAttendeeName("John Wick") .blockingFirst(); assertEquals("John Wick", attendee.getFirstName() + attendee.getLastName());
So, what this code is doing is blocking the thread till the first attendee is provided in the stream and then checking that the attendee is actually John Wick.
While this code works, this is not reactive. With the reactive way of testing, not only you can test more complex logic than this with less verbosity, but it naturally provides ways to test other behaviors of Reactive streams such as subscriptions, errors, completions, etc. We’ll only be covering a few. So, let’s see the reactive version of the above test.
attendeeRepository.getByAttendeeName("John Wick") .firstElement() .test() .assertNoErrors() .assertValue(attendee -> "John Wick".equals( attendee.getFirstName() + attendee.getLastName() ));
So clean and complete. Just by calling test() on the returned observable, we got this whole suite of testing methods with which, not only did we test the name but also that there are no errors while getting the attendee.
Testing for Network Error on loading of Attendees
OK, so let’s move towards a more realistic test. Suppose that you call this method on AttendeeRepository, and that you can fetch attendees from the network. So first, you want to handle the simplest case, that there should be an error if there is no connection. So, if you have (hopefully) set up your project using abstractions for the model using MVP, then it’ll be a piece of cake to test this. Let’s suppose we have a networkUtil object with a method isConnected.
The NetworkUtil class is a dependency of AttendeeRepository and we have set it up as a mock in our test using Mockito. If this is sounding somewhat unfamiliar, please read my previous article “The Joy of Testing with MVP”.
So, our test will look like this
@Test public void shouldStreamErrorOnNetworkDown() { when(networkUtils.isConnected()).thenReturn(false); attendeeRepository.getAttendees() .test() .assertErrorMessage("No Network"); }
Note that, if you don’t define the mock object’s behavior like I have here, attendeeRepository will likely throw an NPE as it will be calling isConnected() on an undefined object.
With RxJava, you get a whole lot of methods for each use case. Even for checking errors, you get to assert a particular Throwable, or a predicate defining an operation on the Throwable, or error message as I have shown in this case.
Now, if you run this code, it’ll probably fail. That’s because if you are testing this by offloading the networking task to a different thread by using subscribeOn observeOn methods, the test body may be detached from Main Thread while the requests complete. Furthermore, if testing in an application made for Android, you would have use AndroidSchedulers.mainThread(), but as it is an Android dependency, the test will fail. Well actually, crash. There were some workarounds by creating abstractions for even RxJava schedulers, but RxJava2 provides a very convenient method to override the default schedulers in the form of RxJavaPlugins. Similarly, RxAndroidPlugins is present in the rx-android package. Let’s suppose you have the plan to use Schedulers.io() for asynchronous work and want to get the stream on Android’s Main Thread, meaning you use AndroidSchedulers.mainThread() in the observeOn method. To override these schedulers to Schedulers.trampoline() which queues your tasks and performs them one by one, like the main thread, your setUp will include this:
RxJavaPlugins.setIoSchedulerHandler(scheduler -> Schedulers.trampoline()); RxAndroidPlugins.setInitMainThreadSchedulerHandler(scheduler -> Schedulers.trampoline());
And if you are not using isolated tests and need to resume the default scheduler behavior after each test, then you’ll need to add this in your tearDown method
RxJavaPlugins.reset(); RxAndroidPlugins.reset();
Testing for Correct loading of Attendees
Now that we have tested that our Repository is correctly throwing an error when the network is down, let’s test that it correctly loads attendees when the network is connected. For this, we’ll need to mock our EventService to return attendees when queried, since we don’t want our unit tests to actually hit the servers.
So, we’ll need to keep these things in mind:
- Mock the network until it shows that it is connected to the Internet
- Mock the EventService to return attendees when queried
- Call the getter on the attendeeRepository and test that it indeed returned a list of attendees
For these conditions, our test will look like this:
@Test public void shouldLoadAttendeesSuccessfully() { List<Attendee> attendees = Arrays.asList( new Attendee(), new Attendee(), new Attendee() ); when(networkUtils.isConnected()).thenReturn(true); when(eventService.getAttendees()).thenReturn(Observable.just(attendees)); attendeeRepository.getAttendees() .test() .assertValues(attendees.toArray(new Attendee[attendees.size()])); }
The assertValues function asserts that these values were emitted by the observable. And if you want to be terser, you can even verify that in fact EventService’s getAttendees function was called by
verify(eventService).getAttendees();
But the problem in this way is that the getAttendees function returns an observable and just calling it does not necessarily means that it was subscribed, emitting the results, hence we need to test to ensure that it was indeed subscribed. If we call the normal test() function on the observable, it is already subscribed, making the result of testSubscribed always true. In order to test that correctly, let’s look at our final use case.
Testing for saving of Attendees
In the Open Event Orga App, we have strived to create self-sufficient and intelligent classes, thus, our repository is also built this way. It detects that new attendees are loaded from the server and saves them in the database. Now we’d want to test this functionality.
In this test, there is an added dependency of DatabaseRepository for saving the attendees, which we will mock. The conditions for this test will be:
- Network is connected
- EventService returns attendees
- DatabaseRepository mocks the saving of attendees
For DatabaseRepository’s save method, we’ll be returning a Completable, which will notify when the saving of data is completed. The primary purpose of this test will be to assert that this completable is indeed subscribed when the attendee loading is triggered. This will not only ensure that the correct function to save the attendees is called, but also that it is indeed triggered and not just left hanging after the call. So, our test will look like this.
@Test public void shouldSaveAttendeesInDatabase() { List<Attendee> attendees = Arrays.asList( new Attendee(), new Attendee(), new Attendee() ); TestObserver testObserver = TestObserver.create(); Completable completable = Completable.complete() .doOnSubscribe(testObserver::onSubscribe); when(networkUtils.isConnected()).thenReturn(true); when(databaseRepository.save(attendees)).thenReturn(completable); when(eventService.getAttendees()).thenReturn(Observable.just(attendees)); attendeeRepository.getAttendees() .test() .assertNoErrors(); testObserver.assertSubscribed(); }
Here, we have created a separate test observable and set it to be subscribed when the Completable is subscribed and we have returned that Completable when the save method is called. In the last, we have asserted that the test observer is indeed subscribed.
You can create more complex use cases and assert subscriptions, errors, the emptiness of a stream and much more, by using the built-in test functionalities of RxJava2. So, that’s all for this blog, you can visit these links for more details on unit testing RxJava
http://fedepaol.github.io/blog/2015/09/13/testing-rxjava-observables-subscriptions/