I'm trying to unit test my code that uses RxJava's Observable#flatMap, but I'm running into java.lang.NullPointerException. How do I write my unit test (and avoid the exception)?
Code to be unit tested:
public void submit(final NameRequest request, final AgeRequest ageRequest) {
model.getAccountName(request)
.flatMap(new Function<NameResponse, Observable<AgeResponse>>() {
#Override
public Observable<AgeResponse> apply(NameResponse response)
throws Exception {
final Status status = response.getStatus();
if (status.getCode() != 1) {
throw new Exception(status.getDescription());
}
return model.getAge(ageRequest);
}
}, new BiFunction<NameResponse, AgeResponse, AgeResponse>() {
#Override
public AgeResponse apply(NameResponse nameResponse, AgeResponse ageResponse) {
ageResponse.setCustomerName(nameResponse.getName());
return ageResponse;
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
}
Test code:
val mockResponse = AgeResponse()
Mockito.doReturn(Observable.just(mockResponse))
.`when`(mNetworkModel)
.preSubmit(ageRequest)
mPresenter.submit(nameRequest, ageRequest)
Related
I am using Compressor library, i want compress the images using RxJava. Following is the example from the library documentation.
new Compressor(this)
.compressToFileAsFlowable(actualImage)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer<File>() {
#Override
public void accept(File file) {
compressedImage = file;
}
}, new Consumer<Throwable>() {
#Override
public void accept(Throwable throwable) {
throwable.printStackTrace();
showError(throwable.getMessage());
}
});
This works very well. Now i want to compress a list of images, how can i use this technique to get a list of compressed file paths?
I tried adding this method in a for loop but the returned list was empty because the accept method was not even called once and the code reached the return statement. Following is my method
#NonNull
private ArrayList<String> compressFiles(ArrayList<String> files, File directory) {
final ArrayList<String> filesToReturn = new ArrayList<>();
for (final String filepath : files) {
new Compressor(this)
.compressToFileAsFlowable(new File(filepath))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer<File>() {
#Override
public void accept(File file) {
filesToReturn.add(file.getAbsolutePath());
}
}, new Consumer<Throwable>() {
#Override
public void accept(Throwable throwable) {
throwable.printStackTrace();
}
});
}
return filesToReturn;
}
How can i change this method using RxJava so accept method only triggers when all the files have been compressed and list is filled?
I tried searching for RxJava loops/Flatmap but i couldn't figure them out. I am new to RxJava. Any help or pointers would be highly appreciated.
In RxJava, you would use Observable.fromIterable():
Observable.fromIterable(files)
.flatMapIterable(files -> files)
.flatMap(filepath -> new Compressor(this).compressToFileAsFlowable(new File(filepath)))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer<File>() {
#Override
public void accept(File file) {
compressedImage = file;
}
}, new Consumer<Throwable>() {
#Override
public void accept(Throwable throwable) {
throwable.printStackTrace();
showError(throwable.getMessage());
}
});
Here are two more possible results:
Completable will be called when all images have been processed
Flowable will push results to subscriber as they are finished processing (honors backpressure)
Please look at subscribeOn. I use the computation scheduler, because cpu bound work should be executed on a bounded threadpool. IO is unbounded.
When you subscribe to Completeable or Flowable you have to apply observeOn with Android-MainUI-Scheduler.
Rember that subscribing to an Flowable/Observable should only be done, when state muste be changed. Otherwise compose observables together.
// Will call complete, when all files finish
private Completable compressFiles(ArrayList<String> files) {
return Flowable.fromIterable(files)
.subscribeOn(Schedulers.io())
.map(new Function<String, File>() {
#Override
public File apply(String s) throws Exception {
return Paths.get(s).toFile();
}
})
.flatMapCompletable(new Function<File, CompletableSource>() {
#Override
public CompletableSource apply(File file) throws Exception {
return new Compressor(appContext)
.compressToFileAsFlowable(file)
.singleOrError()
.toCompletable();
}
});
}
// Will push each file to subscriber as one finishes
private Flowable<File> compressFiles2(ArrayList<String> files) {
return Flowable.fromIterable(files)
.subscribeOn(Schedulers.io())
.map(new Function<String, File>() {
#Override
public File apply(String s) throws Exception {
return Paths.get(s).toFile();
}
})
.flatMap(new Function<File, Publisher<? extends File>>() {
#Override
public Publisher<? extends File> apply(File file) throws Exception {
return new Compressor(appContext)
.compressToFileAsFlowable(file)
.subscribeOn(Schedulers.computation());
}
});
}
In Android, I want to use the call AdvertisingIdClient.getAdvertisingIdInfo(getContext()).getId() on a separate thread (IO-thread) and handle the string on the main thread.
I wan't to do this with RxJava2.
This is what I have now: (which works)
SingleOnSubscribe<String> source = new SingleOnSubscribe<String>() {
#Override
public void subscribe(SingleEmitter<String> e) throws Exception {
e.onSuccess(AdvertisingIdClient.getAdvertisingIdInfo(getContext()).getId());
}
};
Single.create(source)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnError(new Consumer<Throwable>() {
#Override
public void accept(Throwable throwable) throws Exception {
Timber.e(throwable.getMessage());
}
})
.subscribe(new Consumer<String>() {
#Override
public void accept(String s) throws Exception {
advertisingId = s;
}
});
What I would prefer, which is purely taste, is if I could "just" create the stream and handle it all in the flow of methods. Such as: (warning, super pseudo code)
Completable
.empty()
.produce(() -> String {
return makeString();
})
.sub/obs-On()...
.subscribe(coolString -> {mVariable = coolString})
So, Make an Observable and turn it into an Observable by executing some function.
Just use defer or fromCallable like in this example:
Observable<String> stringObservable = Observable.fromCallable(() -> {
return getStuff();
});
Test
#Test
public void fromCallable() throws Exception {
Observable<String> stringObservable = Observable.fromCallable(() -> {
return getStuff();
});
ExecutorService executorService = Executors.newSingleThreadExecutor(runnable -> {
return new Thread(runnable, "myFancyThread");
});
Scheduler scheduler = Schedulers.from(executorService);
TestObserver<String> test = stringObservable.subscribeOn(scheduler)
.test();
test.await()
.assertResult("wurst");
assertThat(test.lastThread().getName()).contains("myFancyThread");
}
private String getStuff() {
return "wurst";
}
The code below won't crash when running in JUnit environment. But it crashes when running in the app. I can see error logs in the console, but tests are marked as passed.
#Test
public void test() {
Observable observable = Observable.error(new RuntimeException());
observable.subscribe();
}
So, the question is: how to make it crash in JUnit. Because yeah, if something doesn't work in the app it's a good thing if it doesn't work in the unit tests also :)
And in this example I have direct access to the observable. But in my real tests I don't have that. Real observables are just internal details of classes that being tested. The most thing I can to do is to inject schedulers or something.
So, how to make it crash without having direct access to the observable?
Also, I've just checked this code doesn't crash either:
#Test
public void test() {
Observable observable = Observable.error(new RuntimeException());
observable.subscribe(new Consumer() {
#Override
public void accept(Object o) throws Exception {
throw new RuntimeException();
}
}, new Consumer<Throwable>() {
#Override
public void accept(Throwable throwable) throws Exception {
throw new RuntimeException();
}
});
}
According to akarnokd this is RxJava2 specific problem.
"Such usages no longer throw synchronously in 2.x but end up in the plugin handler for errors."
It is possible to check if any errors was thrown with this code
public static List<Throwable> trackPluginErrors() {
final List<Throwable> list = Collections.synchronizedList(new ArrayList<Throwable>());
RxJavaPlugins.setErrorHandler(new Consumer<Throwable>() {
#Override
public void accept(Throwable t) {
list.add(t);
}
});
return list;
}
A small trick I use to tackle this problem, is creating a JUnit4 TestRule class that setups a custom RxJava error handler so it can throw when an unhandled RxJava error occurs:
/**
* A rule that detects RxJava silent errors and reports them to JUnit
(by throwing them).
*/
public class RxErrorRule implements TestRule {
#Override
public Statement apply(Statement base, Description description) {
return new Statement() {
#Override
public void evaluate() throws Throwable {
Consumer<? super Throwable> previous = RxJavaPlugins.getErrorHandler();
AtomicReference<Throwable> result = setupErrorHandler();
try {
base.evaluate();
} finally {
RxJavaPlugins.setErrorHandler(previous);
}
if (result.get() != null) {
throw result.get();
}
}
};
}
private AtomicReference<Throwable> setupErrorHandler() {
AtomicReference<Throwable> result = new AtomicReference<>();
RxJavaPlugins.setErrorHandler(new Consumer<Throwable>() {
#Override
public void accept(Throwable throwable) {
result.set(throwable);
}
});
return result;
}
}
And in the unit test:
public class YourRxTest {
#Rule
public RxErrorRule errorRule = new RxErrorRule();
// ...
}
Use TestSubscriber
Observable observable = Observable.error(new RuntimeException());
TestSubscriber testSubscriber = TestSubscriber.create();
observable.subscribe(testSubscriber);
testSubscriber.assertTerminalEvent();
testSubscriber.assertError(RuntimeException.class);
I have some code that first has to run on AndroidSchedulers.mainThread(), then has to do a HTTP request, so has to run on Schedulers.io(), and handle the result on UI, so back to AndroidSchedulers.mainThread().
I receive InterruptedIOException when switching from AndroidSchedulers.mainThread() to Scheulers.io().
Here's some code:
Model model = getModel();
Completable.fromAction(
new Action0() {
public void call() {
mSubject.onNext(model)
}
})
.subscribeOn(AndroidSchedulers.mainThread())
.observeOn(Schedulers.io())
.andThen(fetchFromServer())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(/* handle success and error */);
...
public <T> Single<T> fetchFromServer() {
Request request = new Request(); // some request from server, not important
return bodyFrom2(request);
}
public <T> Single<T> bodyFrom2(final Request<T> request) {
return Single.defer(new Callable<Single<T>>() {
#Override
public Single<T> call() throws Exception {
try {
Response<T> response = request.execute();
if (response.error() != null)
return Single.error(response.error().getMessage());
else {
return Single.just(response.body());
}
} catch (IOException e) {
return Single.error(e);
}
}
});
}
public static <T> Single<T> bodyFrom1(final Request<T> request) {
return Single.create(new Single.OnSubscribe<T>() {
#Override
public void call(SingleSubscriber<? super T> subscriber) {
try {
Response<T> response = request.execute();
if (subscriber.isUnsubscribed())
return;
if (response.error() != null)
subscriber.onError(response.error().getMessage());
else {
subscriber.onSuccess(response.body());
}
} catch (IOException e) {
if (subscriber.isUnsubscribed())
return;
subscriber.onError(e);
}
}
});
}
The exception is thrown in bodyFrom() (1 or 2), at request.execute().
I used bodyFrom1(), but I found this question on SO and thought about trying with the second one. Regardless, I receive the exception.
Trying to find what and where the problem is, I tried this:
Completable.complete()
.subscribeOn(AndroidSchedulers.mainThread())
.observeOn(Schedulers.io())
.andThen(fetchFromServer())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(/* handle success and error */);
which still throws InterruptedIOException, and this:
Completable.complete()
.subscribeOn(Schedulers.computation())
.observeOn(Schedulers.io())
.andThen(fetchFromServer())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(/* handle success and error */);
which works fine.
EDIT:
It seems to work if I'm using Observable or Single instead of Completable.
Added an issue on RxAndroid's Github.
In my Android application I have set up Volley.
Robolectric.application is initialized and all other tests runs smoothly.
I get this error when trying to get mocked HTTP response.
This is my test:
#RunWith(MyRobolectricTestRunner.class)
public class ApiTests {
#Inject
protected Api api;
#Before
public void setUp() {
ObjectGraph.create(new AndroidModule(Robolectric.application), new TestApplicationModule()).inject(this);
}
#Test
public void shouldGetErrorList() throws Exception {
Project project = new Project("test", "test", "test", DateTime.now());
addPendingProjectsErrorsResponse("response.json"); //adding response to FakeHttpLayer
api.getProjectErrors(project, new Listener<ProjectErrors>() {
#Override
public void onResponse(ProjectErrors response) {
assertNotNull(response);
}
}, new ErrorListener() {
#Override
public void onErrorResponse(VolleyError error) {
throw new RuntimeException(error);
}
}
);
}
}
This is error I get:
Exception in thread "Thread-3" java.lang.NullPointerException
at org.robolectric.shadows.ShadowLooper.getMainLooper(ShadowLooper.java:59)
at android.os.Looper.getMainLooper(Looper.java)
at org.robolectric.Robolectric.getUiThreadScheduler(Robolectric.java:1301)
at org.robolectric.shadows.ShadowSystemClock.now(ShadowSystemClock.java:15)
at org.robolectric.shadows.ShadowSystemClock.uptimeMillis(ShadowSystemClock.java:25)
at org.robolectric.shadows.ShadowSystemClock.elapsedRealtime(ShadowSystemClock.java:30)
at android.os.SystemClock.elapsedRealtime(SystemClock.java)
at com.android.volley.VolleyLog$MarkerLog.add(VolleyLog.java:114)
at com.android.volley.Request.addMarker(Request.java:174)
at com.android.volley.CacheDispatcher.run(CacheDispatcher.java:92)
I had same error and avoid it by using my own (and ugly) SystemClock shadow.
shadow class:
#Implements(value = SystemClock.class, callThroughByDefault = true)
public static class MyShadowSystemClock {
public static long elapsedRealtime() {
return 0;
}
}
test code:
#Test
#Config(shadows = { MyShadowSystemClock.class, ... })
public void myTest() {
}
Another workaround would be to disable Volley logging by calling
VolleyLog.DEBUG = false;
in your setUp method.