Mock Retrofit Observable<T> response in Android Unit tests - android

I have an API interface and I'm testing a View that involves network calls.
#Config(emulateSdk = 18)
public class SampleViewTest extends RobolectricTestBase {
ServiceApi apiMock;
#Inject
SampleView fixture;
#Override
public void setUp() {
super.setUp(); //injection is performed in super
apiMock = mock(ServiceApi.class);
fixture = new SampleView(activity);
fixture.setApi(apiMock);
}
#Test
public void testSampleViewCallback() {
when(apiMock.requestA()).thenReturn(Observable.from(new ResponseA());
when(apiMock.requestB()).thenReturn(Observable.from(new ResponseB());
AtomicReference<Object> testResult = new AtomicReference<>();
fixture.updateView(new Callback() {
#Override
public void onSuccess(Object result) {
testResult.set(result);
}
#Override
public void onError(Throwable error) {
throw new RuntimeException(error);
}
});
verify(apiMock, times(1)).requestA();
verify(apiMock, times(1)).requestB();
assertNotNull(testResult.get());
}
}
For some reason apiMock methods are never called and verification always fails.
In my view I'm calling my api like this
apiV2.requestA()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer());
What am I missing here?
Update #1:
After some investigation it appears that when in my implementation (sample above) I observeOn(AndroidSchedulers.mainThread()) subscriber is not called. Still do not know why.
Update #2:
When subscribing just like that apiV2.requestA().subscribe(new Observer()); everything works just fine - mock api is called and test passes.
Advancing ShadowLooper.idleMainLooper(5000) did nothing. Even grabbed looper from handler in HandlerThreadScheduler and advanced it. Same result.
Update #3:
Adding actual code where API is used.
public void updateView(final Callback) {
Observable.zip(wrapObservable(api.requestA()), wrapObservable(api.requestB()),
new Func2<ResponseA, ResponseB, Object>() {
#Override
public Object call(ResponseA responseA, ResponseB responseB) {
return mergeBothResponses(responseA, responseB);
}
}
).subscribe(new EndlessObserver<Object>() {
#Override
public void onError(Throwable e) {
Log.e(e);
listener.onError(e);
}
#Override
public void onNext(Object config) {
Log.d("Configuration updated [%s]", config.toString());
listener.onSuccess(config);
}
});
}
protected <T> Observable<T> wrapObservable(Observable<T> observable) {
return observable.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread());
}

I'm still wrapping my head around how to properly use rxjava myself but I would try to modify your code so that you only observeOn(mainThread) on the final zipped Observable instead of doing it on both of the original request response's Observable. I would then verify if this affect the fact that you have to advance both Loopers or not.
To simply your tests and remove the need for Looper idling I would take the threading out of the equation since you don't need background processing when running tests. You can do that by having your Schedulers injected instead of creating them statically. When running your production code you'd have the AndroidSchedulers.mainThread and Schedulers.io injected and when running tests code you would inject Schedulers.immediate where applicable.
#Inject
#UIScheduler /* Inject Schedulers.immediate for tests and AndroidSchedulers.mainThread for production code */
private Scheduler mainThreadSched;
#Inject
#IOScheduler /* Inject Scheduler.immediate for tests and Schedulers.io for production code */
private Scheduler ioSched;
public void updateView(final Callback) {
Observable.zip(wrapObservable(api.requestA()), wrapObservable(api.requestB()),
new Func2<ResponseA, ResponseB, Object>() {
#Override
public Object call(ResponseA responseA, ResponseB responseB) {
return mergeBothResponses(responseA, responseB);
}
}
).observeOn(mainThreadSched)
.subscribe(new EndlessObserver<Object>() {
#Override
public void onError(Throwable e) {
Log.e(e);
listener.onError(e);
}
#Override
public void onNext(Object config) {
Log.d("Configuration updated [%s]", config.toString());
listener.onSuccess(config);
}
});
}
protected <T> Observable<T> wrapObservable(Observable<T> observable) {
return observable.subscribeOn(ioSched);
}

what version of rxjava are you using? I know there was some changes in the 0.18.* version regarding the ExecutorScheduler. I had a similar issue as you when using 0.18.3 where I wouldn't get the onComplete message because my subscription would be unsubscribe ahead of time. The only reason I'm mentioning this to you is that a fix in 0.19.0 fixed my issue.
Unfortunately I can't really explain the details of what was fixed, it's beyond my understanding at this point but if it turns out to be the same cause maybe someone with more understand could explain. Here's the link of what I'm talking about https://github.com/Netflix/RxJava/issues/1219.
This isn't much of an answer but more a heads up in case it could help you.

As #champ016 stated there were issues with RxJava versions that are lower than 0.19.0.
When using 0.19.0 the following approach works. Although still don't quite get why I have to advance BOTH loopers.
#Test
public void testSampleViewCallback() {
when(apiMock.requestA()).thenReturn(Observable.from(new ResponseA());
when(apiMock.requestB()).thenReturn(Observable.from(new ResponseB());
AtomicReference<Object> testResult = new AtomicReference<>();
fixture.updateView(new Callback() {
#Override
public void onSuccess(Object result) {
testResult.set(result);
}
#Override
public void onError(Throwable error) {
throw new RuntimeException(error);
}
});
ShadowLooper.idleMainLooper(5000);
Robolectric.shadowOf(
Reflection.field("handler")
.ofType(Handler.class)
.in(AndroidSchedulers.mainThread())
.get().getLooper())
.idle(5000);
verify(apiMock, times(1)).requestA();
verify(apiMock, times(1)).requestB();
assertNotNull(testResult.get());
}

Related

How to make two Retrofit calls and combine results?

I've been looking around StackOverflow and other Android-related sites to try and get a grasp on this, but I'm still struggling.
I'm using Retrofit to make calls to an API as follows:
public interface TheMovieDbApi {
#GET("genre/{type}/list")
Observable<GenresReply<Genre>> getGenreList(#Path("type") String type);
}
The above example returns an Object (GenresReply) which contains a List of Genres.
I need to make this call twice - once for movies, once for TV - and combine the results. Having looked at other examples here, I've come up with the following:
private void loadGenres() {
List<Observable<?>> requests = new ArrayList<>();
requests.add(api.getGenreList("movie"));
requests.add(api.getGenreList("tv"));
//Now what?
}
I'm lost on the next step. I've seen examples using Observable.concat(), .flatMap() and .zip() and then subscribing to the output, but I'm not familiar enough with RxJava to know what to do next.
TL;DR How do I make two API calls and extract the List of Genres from each response/the combined List of Genres?
Solution
Thanks to the comments from John and masp, here's what I've come up with:
private void loadGenres() {
Observable.zip(api.getGenreList(MOVIE_GENRES), api.getGenreList(SHOW_GENRES),
new BiFunction<GenresReply<Genre>, GenresReply<Genre>, List<Genre>>() {
#Override
public List<Genre> apply(GenresReply<Genre> movieReply, GenresReply<Genre> showReply)
throws Exception {
List<Genre> genreList = new ArrayList<>();
genreList.addAll(movieReply.getGenres());
genreList.addAll(showReply.getGenres());
return genreList;
}
}).subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<List<Genre>>() {
#Override
public void onSubscribe(#NonNull Disposable d) {
}
#Override
public void onNext(#NonNull List<Genre> genres) {
DatabaseUtils.insertGenres(genres, ListActivity.this);
}
#Override
public void onError(#NonNull Throwable e) {
mSharedPreferences.edit().putBoolean(FIRST_RUN, true).apply();
}
#Override
public void onComplete() {
}
});
}
You should be able to do something like:
Observable.zip(api.getGenreList("movie", api.getGenreList("tv", (movieInfo, tvInfo) -> Pair.create(movieInfo, tvInfo)).subscribe(movieTvPair -> {
})

Error utilizing RealmDB while unit testing

I am writing a test for a ViewModel. The function in the ViewModel is this:
public void discoverMovies(boolean showLoading) {
// reset the states to initial states
moviesLoading.set(showLoading);
errorViewShowing.set(false);
emptyViewShowing.set(false);
mMoviesRepository.getPopularMovies(1)
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.subscribeWith(new DisposableObserver<List<Movie>>() {
#Override
public void onNext(List<Movie> value) {
// show or hide empty view
boolean isEmpty = value == null || value.isEmpty();
if (!isEmpty) {
saveResponse(value);
movies.clear();
movies.addAll(value);
}
emptyViewShowing.set(isEmpty);
}
#Override
public void onError(Throwable throwable) {
errorViewShowing.set(true);
moviesLoading.set(false);
emptyViewShowing.set(false);
errorString.set(getErrorMessage(throwable));
}
#Override
public void onComplete() {
moviesLoading.set(false);
errorViewShowing.set(false);
}
});
}
private void saveResponse(final MovieResponse mainResponse) {
Realm.getDefaultInstance().executeTransaction(new Realm.Transaction() {
#Override public void execute(Realm realm) {
RealmMovie realmMovie = realm.createObject(RealmMovie.class);
realmMovie.setId(1);
realmMovie.setMarvelResponse(new Gson().toJson(mainResponse));
}
});
}
And I test the function above in my test class like this:
Note: Everything works without the Realm aspect. I've confirmed that.
#Test
public void getPopularMoviesWithoutError() {
// given the following movies
when(mMoviesRepository.getPopularMovies(PAGE)).thenReturn(Observable.just(MOVIES));
// discover popular movies
mMoviesViewModel.discoverMovies(true);
// verify that the repository is called
verify(mMoviesRepository).getPopularMovies(PAGE);
// test that the loading indicator is hidden
assertFalse(mMoviesViewModel.moviesLoading.get());
// check that the empty view is hidden
assertFalse(mMoviesViewModel.emptyViewShowing.get());
// check that the error view is hidden
assertFalse(mMoviesViewModel.errorViewShowing.get());
assertTrue(mMoviesViewModel.movies.size() == MOVIES.size());
}
And it keeps on giving me java.lang.IllegalStateException: CallRealm.init(Context)before calling this method. How can I initialize Realm
to be available
I think the error message you are getting is quite clear about what is causing the problem. You are not calling Realm.init.
There are several ways of doing this. The simplest is the #Before and #After annotations on the test suite. You could also use a TestRule
Unfortunately, Realm.init requires a Context. To get that context, you are going to have to be in some environment that has one. That means that you will either have to run your tests on a device, as Instrumentation tests or, as #David Rawson suggests, use Robolectric.

RxAndroid Subscribe code never called

I'm fairly new to RxJava and RxAndroid, and while some things work, I'm now completely stumped by what I see as basic functionality not working.
I have a subscribe call on a Subject that never seems to run, and I can't figure out why:
public class PairManager implements DiscoveryManagerListener {
private Subscription wifiAvailableSubscription;
private Subscription debugSubscription;
private DiscoveryManager discoveryManager;
private AsyncSubject<Map<String, ConnectableDevice>> availableDevices;
public PairManager(Context appContext) {
DiscoveryManager.init(appContext);
discoveryManager = DiscoveryManager.getInstance();
discoveryManager.addListener(this);
availableDevices = AsyncSubject.<Map<String, ConnectableDevice>> create();
//
// This subscription doesn't work
//
debugSubscription = availableDevices
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Action1<Map<String, ConnectableDevice>>() {
#Override
public void call(Map<String, ConnectableDevice> stringConnectableDeviceMap) {
//
// This code is never run !
//
Timber.d(">> Available devices changed %s", stringConnectableDeviceMap);
}
}, new Action1<Throwable>() {
#Override
public void call(Throwable throwable) {
Timber.d("Subscription failed %s", throwable);
}
});
availableDevices.onNext(Collections.<String, ConnectableDevice>emptyMap());
wifiAvailableSubscription = ReactiveNetwork.observeNetworkConnectivity(appContext)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Action1<Connectivity>() {
#Override
public void call(Connectivity connectivity) {
if (connectivity.getState().equals(NetworkInfo.State.CONNECTED) && connectivity.getType() == ConnectivityManager.TYPE_WIFI) {
discoveryManager.start();
} else {
discoveryManager.stop();
availableDevices.onNext(Collections.<String, ConnectableDevice>emptyMap());
}
}
});
}
public AsyncSubject<Map<String, ConnectableDevice>> getAvailableDevices() {
return availableDevices;
}
#Override
public void onDeviceAdded(DiscoveryManager manager, ConnectableDevice device) {
Timber.d("onDeviceAdded %s", device);
availableDevices.onNext(manager.getAllDevices());
Timber.d("Sanity check %s", availableDevices.getValue());
}
// ...
}
Is there a way to debug what is going wrong? I have tried creating basic Observable.from-type calls and logging those, and that works as expected. The sanity check log in onDeviceAdded also prints and indicates that availableDevices has in fact updated as expected. What am I doing wrong?
I've found the issue, I've used AsyncSubjects which only ever emit values when they are Completed, where I expect the functionality of BehaviorSubjects.
From the doccumentation:
When Connectivity changes, subscriber will be notified. Connectivity can change its state or type.
You say:
I have a subscribe call on a Subject
A subject won't return te last value. I will only return a value when onNext is called. I assume the Connectivity never changes so it never fires.

Observable android understanding

I'm trying to understand how the observer pattern works in Android.
I've created this method to load a sample list of object, pushing each items to the subscriber and loading it to into the recyclerview.
I don't understand why if i load 10 items everything is working fine, but if i load 100/1000 or in general more items, the recyclerView is empty and onNext, onComplete are not fired.
private Observable<AppInfo> getAppList() {
return Observable.create(new Observable.OnSubscribe<AppInfo>() {
#Override
public void call(Subscriber<? super AppInfo> subscriber) {
for (int i = 0; i<10; i++){
AppInfo appInfo = new AppInfo(
"Test item "+i,
ContextCompat.getDrawable(getApplicationContext(), R.mipmap.ic_launcher),
i
);
subscriber.onNext(appInfo);
}
if (!subscriber.isUnsubscribed()) {
subscriber.onCompleted();
}
}
});
}
And this is how i use the Observable:
Observable<AppInfo> appInfoObserver = getAppList();
appInfoObserver
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<AppInfo>() {
#Override
public void onCompleted() {
Toast.makeText(getApplicationContext(), "App List Load Completed!", Toast.LENGTH_LONG).show();
}
#Override
public void onError(Throwable e) {}
#Override
public void onNext(AppInfo appInfo) {
if(mAppInfoList != null){
mAppInfoList.add(appInfo);
adapter.notifyItemInserted(appInfo.getAppPosition());
}
}
});
Thanks for the help and advices.
You're not logging errors so if anything goes wrong you won't know (in this case you are probably forcing a MissingBackpressureException from the observeOn operator by sending it more than it requested). To be clear, in the subscriber:
public void onError(Throwable e) {
// log or display error here!!
}
Don't use Observable.create at all if you can help it because you need to honour backpressure or combine it with .onBackpressureBuffer.
The exception is that Observable.create(new SyncOnSubscribe<T>(...)) is a good way to create an Observable if you can imagine your source as an iterator/enumeration.
To avoid using Observable.create in your example you could do this:
Observable
.range(0, 10)
.map(i -> new AppInfo(...))
or without lambda:
Observable
.range(0, 10)
.map(new Func1<Integer, AppInfo>() {
#Override
public AppInfo call(Integer n) {
return new AppInfo(...);
}
});
Maybe your code is to heavy and its loading sync. Try to load your code inside a new thread, maybe you can use the observeOn() (i dont know exactally how rxjava works, but my guess is that this function defines the thread where the event occurs).

Why does the Observable not create on the right thread?

Observable observable = Observable.from(backToArray(downloadWebPage("URL")))
.map(new Func1<String[], Pair<String[], String[]>>() {
#Override
public Pair<String[], String[]> call(String[] of) {
return new Pair<>(of,
backToArray(downloadWebPage("URL" + of[0])).get(0));
}
});
observable.subscribeOn(Schedulers.newThread()).observeOn(AndroidSchedulers.mainThread()).subscribe(
(new Observer<Pair>() {
#Override
public void onCompleted() {
// Update user interface if needed
}
#Override
public void onError(Throwable t) {
// Update user interface to handle error
}
#Override
public void onNext(Pair p) {
offices.add(new Office((String[]) p.first, (String[]) p.second));
}
}));
This runs and i get android.os.NetworkOnMainThreadException. I would expect it to run a new thread as set by the subscribeOn() method.
Assuming that the actual network request is happening in downloadWebPage(), the error is in the first line of your code:
Observable observable = Observable.from(backToArray(downloadWebPage("http://api.ataxcloudapp.com/v1/franchise/listing/?location=" + ZIPCode)))
This is equivalent to:
String[] response = downloadWebPage("http://api.ataxcloudapp.com/v1/franchise/listing/?location=" + ZIPCode)
Observable observable = Observable.from(backToArray(response))
This should make it clear that downloadWebPage is executed - on the main thread - before any Observable is even created, let alone subscribed to. RxJava cannot change the semantics of Java in this regard.
What you can do however is something like this (not tested, but should be about right):
Observable observable = Observable.create(new Observable.OnSubscribe<String[]>() {
#Override
public void call(final Subscriber<? super String[]> subscriber) {
final String[] response = downloadWebPage("http://api.ataxcloudapp.com/v1/franchise/listing/?location=" + ZIPCode);
if (! subscriber.isUnsubscribed()) {
subscriber.onNext(backToArray(response));
subscriber.onCompleted();
}
}
)
Now your network request will happen only after the Observable is subscribed to, and will be moved to a the thread you specify in subscribeOn().
You can use defer() to postpone the calling of downloadWebPage to the moment when you subscribe to the observable.
Example:
private Object slowBlockingMethod() { ... }
public Observable<Object> newMethod() {
return Observable.defer(() -> Observable.just(slowBlockingMethod()));
}
Source
You should change from
**observable.subscribeOn(Schedulers.newThread())**
to
**observable.subscribeOn(Schedulers.io())**

Categories

Resources