I'm trying to write some tests for an Android application where we are integrating Bolts.
The object under test works by creating a Task and executing it in the background:
Task<MyResult> currentTask;
currentTask = MyTaskFactory.getImportantTask(parameters ...);
currentTask.continueWith(new Continuation<MyResult,MyResult>() {
#Override
public MyResult then(Task<MyResult> task) throws Exception {
MyResult result = task.getResult();
if (!task.isFaulted()) {
if (DEBUG) {
logger.v("got login result back: " + result.toString());
}
if (result.getSuccess()) {
callback.onMySuccess(result.getData());
} else {
callback.onMyError(result.getStatusCode(), result.getError());
}
}
return result;
}
}, Task.UI_THREAD_EXECUTOR);
MyTaskFactory.getImportantTask() returns a Task, and my object under test gets this task and executes it in the background. When it completes, the completion should get executed.
I have verified that in the actual app, this code is working correctly. My problem arises because I am trying to run this under Robolectric/Mockito.
Here is the code under test:
myobject.runTaskWithContinuation(parameters...);
ShadowLog.d(TAG, "Waiting....");
Task<AuthenticationResult> task = controller.getCurrentTask();
assert(task != null);
ShadowApplication.runBackgroundTasks();
ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
ShadowLog.d(TAG, "Finished!");
Using this code, and by mocking and instrumenting the calls made by the Task and the Continuation, I have verified that the Task is executing and generating a result, yet the Continuation never gets called. If I wait for the Task to complete, my unit test will never finish.
I am certain that I am doing something wrong here, but I really don't know what. How can I make sure that the Continuation gets called under test?
In your unit tests, since you have access to the Task directly, call
task.waitForCompletion()
For example:
#Test
public void directTaskAccess() throws InterruptedException {
Task<Boolean> task = doWorkTask();
task.waitForCompletion();
assertTrue(task.getResult());
}
public Task<Boolean> doWorkTask() {
return Task.callInBackground(() -> {
// simulate doing 'hard' work
Thread.sleep(1000);
return true;
});
}
For other tests when you don't have access to the Task reference, you might need to do a little extra work.
This blog post details more about it: Unit Testing Asynchronous Bolts Tasks
Use a CountDownLatch to make the Test thread to wait until your Continuation has been reached
here's a sample code that runs on my Robolectric test:
#Test
public void test1() throws Throwable {
success = false;
final CountDownLatch signal = new CountDownLatch(1);
task.onSuccess(new Continuation<Object, Object>() {
#Override
public Object then(Task<Object> task) throws Exception {
if (condition == true) {
success = true;
}
signal.countDown();
return null;
}
});
signal.await(30, TimeUnit.SECONDS);
assertTrue(success);
}
Related
I am new in RX Java and I have a issue.I have a completable and want to continue my operations after I got a value and did some actions in one of the steps of emmition.
if (mIsCopy) {
completable = AppManagers.getContentManager().completeCopy(mContent).toCompletable().andThen(completable).doOnComplete(() -> {
createThumbnailsForNewContent(mContent); //I want this to be happen before completable.cache();
});
}
completable = completable.cache();
completable.subscribe(new SimpleCompletableSubscriber() {
#Override
public void onComplete() {
TypeUtil.empty(mNewTags);
mOriginalSubmitToUsers.clear();
mOriginalSubmitToUsers.addAll(mSubmitToUsers);
mOriginalSubmitToGroups.clear();
mOriginalSubmitToGroups.addAll(mSubmitToGroups);
mOriginalSubmitToChannels.clear();
mOriginalSubmitToChannels.addAll(mSubmitToChannels);
mOriginalSubmitToRelationships.clear();
mOriginalSubmitToRelationships.addAll(mSubmitToRelationships);
mIsCopy = false;
}
createThumbnailsForNewContent(mContent); returns completable,thx in advance.
public Single<Long> completeCopy(final Content copiedContent) {
return Single.fromCallable(new Callable<Long>() {
#Override
public Long call() throws Exception {
if (!copiedContent.isLocalCopy() || copiedContent.getId() != null) {
throw new IllegalArgumentException("Content passed must be an unsaved local copy.");
}
if (getDBManager().insertOrUpdateContent(copiedContent)) {
if (copiedContent.getCopiedFromCloudID() != null && copiedContent.getCopiedFromCloudID() > 0) {
AppManagers.getAppContext()
.getRequestQueue()
.forRequest(new InitiateCopyRequest(AppUserData.shared.getAccessKey(),
copiedContent.getCopiedFromCloudID()))
.withPriority(RequestQueue.Priority.UPDATE)
.attachLocalID(copiedContent.getId())
.enqueue();
}
} else {
throw new ErrorCodeException(ErrorCodes.UPDATE_CONTENT_FAILED);
}
return copiedContent.getId();
}
}).subscribeOn(getIOScheduler());
}
Don't really get the problem, but according to your code it seems that the method createThumbnailsForNewContent is never executed. That's because doOnComplete accepts as param an Action (this is a Runnable but allows throwing checked exceptions).
If you want to execute createThumbnailsForNewContent once the previous Completable is completed, you have to use the operator andThen(CompletableSource next).
I am developing a splash screen for an Android app which has a few requirements:
While the splash screen is showing I need to fetch a value from a
repository. Once that value is fetched it will be used to kick off 3
different calls to the repository in parallel.
The splash screen
must show for at least 3 seconds or as long as it takes to fetch all
values from the repository (if the fetches take longer than 3
seconds).
If an error occurs when fetching the first value from
the repository then the observable chain can terminate (once 3
seconds have elapsed)
If any errors occur when fetching the next
three values the observable chain should not terminate, that is, all
three should run even if one or more fail.
Here's what I have currently:
Observable<Long> timerObservable = Observable.timer(3, TimeUnit.SECONDS);
cachedObservable = mLoginRepository
.fetchLoginPreference()
.onErrorReturn(new Func1<Throwable, LoginPreference>() {
#Override
public LoginPreference call(Throwable throwable) {
return null;
}
})
.flatMap(new Func1<LoginPreference, Observable<CompositeLPLCPalette>>() {
#Override
public Observable<CompositeLPLCPalette> call(final LoginPreference loginPreference) {
if (loginPreference == null
|| loginPreference.getActivationCode() == null
|| loginPreference.getActivationCode().isEmpty()) {
Timber.d("login preference was null");
return Observable.just(null);
}
final String activationCode = loginPreference.getActivationCode();
Observable<LoginCapability> loginCapabilityObservable = mLoginRepository
.fetchLoginCapability(activationCode, true)
.onErrorReturn(new Func1<Throwable, LoginCapability>() {
#Override
public LoginCapability call(Throwable throwable) {
return null;
}
});
Observable<OrgContactInfo> orgContactInfoObservable = mLoginRepository
.fetchOrgContactInfo(activationCode, true)
.onErrorReturn(new Func1<Throwable, OrgContactInfo>() {
#Override
public OrgContactInfo call(Throwable throwable) {
Timber.d("Error fetching org contact info");
return null;
}
});
Observable<Palette> paletteObservable = mLoginRepository
.fetchThemeInformation(activationCode, true)
.onErrorReturn(new Func1<Throwable, Palette>() {
#Override
public Palette call(Throwable throwable) {
Timber.d("Error fetching Palette");
return null;
}
});
return Observable.zip(loginCapabilityObservable,
paletteObservable,
orgContactInfoObservable,
new Func3<LoginCapability, Palette, OrgContactInfo, CompositeLPLCPalette>() {
#Override
public CompositeLPLCPalette call(LoginCapability loginCapability, Palette palette, OrgContactInfo orgContactInfo) {
return new CompositeLPLCPalette(loginCapability, loginPreference, palette);
}
});
}
})
.zipWith(timerObservable, new Func2<CompositeLPLCPalette, Long, CompositeLPLCPalette>() {
#Override
public CompositeLPLCPalette call(CompositeLPLCPalette compositeLPLCPalette, Long aLong) {
return compositeLPLCPalette;
}
});
The code above works but I have a few questions:
1) Is the way I'm enforcing the 3 second minimum the correct way to do it? It looked like there was a delay operator as well as the timer operator and it wasn't clear which I should use. Also, should I be zipping the timer operator with the rest of the chain?
2) Am I using onErrorReturn() correctly if my intention is that if the observable fails it should just return null instead of having the subscriber's onError() method?
3) In the flatMap() operator I'm checking to see if loginPreference is null, has a null activation code or empty activation code and if any of those things are true I don't want to run the other 3 observables. Is there a different operator I should be using before the flatMap() operator instead of adding this logic to flatMap()?
Yep, that's the correct way to do it. I would suggest extracting the contents of the flatMap as a separate method, and keep the 2 zip operators separate for logic and maintenance reasons.
I really don't like having nulls in observables, at a minimum it's incompatible with RxJava 2. However given the requirements the logic feels sound.
Nah, you're good.
I have a following class that my coworker created while we were using Retrofit 1.9
public class SomeApiCallAction {
private Subscription subscription;
private NoInternetConnectionInterface noInternetConnectionInterface;
public interface NoInternetConnectionInterface {
PublishSubject<Integer> noInternetConnection(Throwable throwable);
}
public void execute(Subscriber subscriber, NoInternetConnectionInterface noInternetConnectionInterface) {
this.noInternetConnectionInterface = noInternetConnectionInterface;
this.subscription = retrofit.someService().someApiCall()
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(subscriber)
.retryWhen(retryFunction);
}
public void cancel() {
if (this.subscription != null) {
this.subscription.unsubscribe();
}
}
private Func1<Observable<? extends Throwable>, Observable<?>> retryFunction = new Func1<Observable<? extends Throwable>, Observable<?>>() {
#Override
public Observable<?> call(Observable<? extends Throwable> observable) {
return observable.flatMap(new Func1<Throwable, Observable<?>>() {
#Override
public Observable<?> call(final Throwable throwable) {
if (noInternetConnectionInterface!= null && (throwable instanceof IOException || throwable instanceof SocketTimeoutException)) {
return noInternetConnectionInterface.noInternetConnection(throwable);
}else{
return Observable.error(throwable);
}
}
});
}
}
SomeApiCallAction is just a simple class that wrap retrofit api call inside, the only thing special is its retry function. The retry function will check if throwable is kind of IOException or SocketTimeoutException or not, if it is, it will call the interface so that we can present retry dialog to user to ask whether they want to retry the operation or not. Our usage is similar to following snippet
public class SomeActivity implement NoInternetConnectionInterface {
#OnClick(R.id.button)
public void do(View v) {
new SomeApiCallAction().execute(
new Subscriber(),
this
)
}
#Override
public PublishSubject<Integer> noInternetConnection(final Throwable throwable) {
Log.i("Dev", Thread.currentThread() + " Error!");
final PublishSubject<Integer> subject = PublishSubject.create();
runOnUiThread(new Runnable() {
#Override
public void run() {
NoInternetDialogFragment dialog = NoInternetDialogFragment.newInstance();
dialog.setNoInternetDialogFragmentListener(new NoInternetDialogFragmentListener{
#Override
public void onUserChoice(boolean retry, NoInternetDialogFragment dialog) {
Log.i("Dev", Thread.currentThread() + " Button Click!");
if (retry) {
subject.onNext(1);
} else {
subject.onError(throwable);
}
dialog.dismiss();
}
});
dialog.show(getSupportFragmentManager(), NoInternetDialogFragment.TAG);
}
});
return subject;
}
}
When we were using Retrofit 1.9.0, this implementation was working perfectly. We test by turn on Airplane Mode and press the button to execute api call.
first execution fail and I got UnknownHostException in retry function.
so, I call the interface (Activity) to present retry dialog
I press retry button while still on Airplane mode to repeat the execution
as expected, every execution that happen after user press retry button failed to, I always get UnknownHostException in retry function.
If I keep pressing the retry button, retry dialog will appears forever until I turn off the airplane mode.
But after we update our dependencies to
'com.squareup.retrofit2:retrofit:2.0.2'
'com.squareup.retrofit2:adapter-rxjava:2.0.2'
We try again but this time the behaviour change,
first execution fail and I got UnknownHostException in retry function same as before.
so, I call the interface (Activity) to present retry dialog
I press retry button while still on Airplane mode to repeat the execution
But this time, in the retry function, instead of receiving UnknowHostException like what it was, I got NetworkOnMainThreadException instead
so the condition is not match, interface not gets call, and result as only 1 retry dialog presented to user.
Following is the log from above code
Thread[android_0,5,main] Error!
Thread[main,5,main] Button Click!
Do you have any idea what would cause this? Any suggestion, comment will be very appreciate.
Note : Following are other dependencies that we been using and might related. But they are not recently updated, been using these version since the beginning of this project.
'com.jakewharton:butterknife:8.0.1'
'io.reactivex:rxandroid:1.1.0'
'io.reactivex:rxjava:1.1.0'
'com.google.dagger:dagger-compiler:2.0'
'com.google.dagger:dagger:2.0'
'javax.annotation:jsr250-api:1.0'
More Info
I just reset my code back to the point when we were using Retrofit 1.9, I found the the print log is different
Thread[Retrofit-Idle,5,main] Error!
Thread[main,5,main] Button Click!
Not sure if this relevant to the issue or not, but clearly that in 1.9.0 I call interface in different thread compare to 2.0.0
Final Edit
After reading the answer from #JohnWowUs and follow to the link he provide I found that in Retrofit 2, network call will be synchronous by default
To resolve my issue, there are 2 ways to do this
1.) Do as #JohnWowUs suggest by specify the thread for retryFunction
this.subscription = retrofit.someService().someApiCall()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(subscriber)
.retryWhen(retryFunction, Schedulers.io());
2.) When create retrofit object, specify thread when create RxJavaCallAdapterFactory
retrofit = new Retrofit.Builder()
.baseUrl(AppConfig.BASE_URL)
.client(client)
.addConverterFactory(GsonConverterFactory.create(getGson()))
.addCallAdapterFactory(
RxJavaCallAdapterFactory.createWithScheduler(
Schedulers.from(threadExecutor)
)
)
.build();
I think the problem is that when you resubscribe you're subscribing on the main thread as a consequence of using the default trampoline scheduler in retryWhen. Retrofit 1.9 handled the scheduling for you so using subscribeOn was pointless. The issue discussion is here. In Retrofit 2 I believe this has changed so you should try something like
this.subscription = retrofit.someService().someApiCall()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(subscriber)
.retryWhen(retryFunction, Schedulers.io());
For a quick solution this is what I do to resolve this issue
new Thread(new Runnable(){
public void run() {
choice.onNext(1);
}
}).start();
The app work as we expect again. But, I don't think this is the right way to resolve this issue so I'll keep this question open for further comment.
I'm trying to use the Facebook / Parse Bolts framework to run multiple tasks in parallel on Android. The documentation for running tasks in parallel seems to indicate that the result of whenAll will always be Void.
Is there a way to get the actual result of one or more of the parallelized Tasks?
You can access the result of the tasks like this:
ArrayList<Task<Boolean>> tasks = getTasks();
Task.whenAll(tasks).onSuccess(new Continuation<Void, Void>() {
#Override
public Void then(Task<Void> task) throws Exception {
for (Task<Boolean> t : tasks) {
doSomething(t.getResult());
}
return null;
}
});
In the continuation on the whenAll, you can get the results directly from the sub-tasks:
final Task<A> a = // ...
final Task<B> b = // ...
Task.whenAll(Arrays.asList(a, b)).continueWithTask((t) -> {
if (!a.isFaulted()) {
return doSomethingAsync(a.getResult());
}
if (!b.isFaulted()) {
return doSomethingElseAsync(b.getResult());
}
// ...
return t;
});
I am trying to integrate Unit test cases for every chunk of code possible.
But I am facing issues while adding test cases for api calls that are made through retrofit.
The JUnit compiler never executes the code in the CallBack functions.
There is another option of making all the api calls Synchronous for testing purpose, but that's not possible for every case in my app.
How can I sort this out? I have to add test cases in the api calls by any means.
If you use .execute() instead of .enqueue() it makes execution synchron, thus the tests can ran properly without the need of importing 3 different libraries and adding any code or modify the build variants.
Like:
public class LoginAPITest {
#Test
public void login_Success() {
APIEndpoints apiEndpoints = RetrofitHelper.getTesterInstance().create(APIEndpoints.class);
Call<AuthResponse> call = apiEndpoints.postLogin();
try {
//Magic is here at .execute() instead of .enqueue()
Response<AuthResponse> response = call.execute();
AuthResponse authResponse = response.body();
assertTrue(response.isSuccessful() && authResponse.getBearer().startsWith("TestBearer"));
} catch (IOException e) {
e.printStackTrace();
}
}
}
I test my Retrofit callbacks using Mockito, Robolectric and Hamcrest libraries.
First of all, set up lib stack in your module's build.gradle:
dependencies {
testCompile 'org.robolectric:robolectric:3.0'
testCompile "org.mockito:mockito-core:1.10.19"
androidTestCompile 'org.hamcrest:hamcrest-library:1.1'
}
In jour project's global build.gradle add following line to buildscript dependencies:
classpath 'org.robolectric:robolectric-gradle-plugin:1.0.1'
Then enter "Build Variants" menu in Android Studio (to quickly find it, hit Ctrl+Shift+A and search for it), and switch "Test Artifact" option to "Unit Tests". Android studio will switch your test folder to "com.your.package (test)" (instead of androidTest).
Ok. Set-up is done, time to write some tests!
Let's say you've got some retrofit api calls to retrieve a list of objects that need to be put into some adapter for a RecyclerView etc. We would like to test whether adapter gets filled with proper items on successful call.
To do this, we'll need to switch your Retrofit interface implementation, that you use to make calls with a mock, and do some fake responses taking advantage of Mockito ArgumentCaptor class.
#Config(constants = BuildConfig.class, sdk = 21,
manifest = "app/src/main/AndroidManifest.xml")
#RunWith(RobolectricGradleTestRunner.class)
public class RetrofitCallTest {
private MainActivity mainActivity;
#Mock
private RetrofitApi mockRetrofitApiImpl;
#Captor
private ArgumentCaptor<Callback<List<YourObject>>> callbackArgumentCaptor;
#Before
public void setUp() {
MockitoAnnotations.initMocks(this);
ActivityController<MainActivity> controller = Robolectric.buildActivity(MainActivity.class);
mainActivity = controller.get();
// Then we need to swap the retrofit api impl. with a mock one
// I usually store my Retrofit api impl as a static singleton in class RestClient, hence:
RestClient.setApi(mockRetrofitApiImpl);
controller.create();
}
#Test
public void shouldFillAdapter() throws Exception {
Mockito.verify(mockRetrofitApiImpl)
.getYourObject(callbackArgumentCaptor.capture());
int objectsQuantity = 10;
List<YourObject> list = new ArrayList<YourObject>();
for(int i = 0; i < objectsQuantity; ++i) {
list.add(new YourObject());
}
callbackArgumentCaptor.getValue().success(list, null);
YourAdapter yourAdapter = mainActivity.getAdapter(); // Obtain adapter
// Simple test check if adapter has as many items as put into response
assertThat(yourAdapter.getItemCount(), equalTo(objectsQuantity));
}
}
Proceed with the test by right clicking the test class and hitting run.
And that's it. I strongly suggest using Robolectric (with robolectric gradle plugin) and Mockito, these libs make testing android apps whole lotta easier.
I've learned this method from the following blog post. Also, refer to this answer.
Update: If you're using Retrofit with RxJava, check out my other answer on that too.
The JUnit framework never executes the code in the CallBack functions because the main thread of execution terminates before the response is retrieved. You can use CountDownLatch as shown below:
#Test
public void testApiResponse() {
CountDownLatch latch = new CountDownLatch(1);
mApiHelper.loadDataFromBackend(new Callback() {
#Override
public void onResponse(Call call, Response response) {
System.out.println("Success");
latch.countDown();
}
#Override
public void onFailure(Call call, Throwable t) {
System.out.println("Failure");
latch.countDown();
}
});
try {
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
This test sample may be helpful too.
My advice isn't to perform testing for the API responses in the android app. There are many external tools for this.
Junit will not wait for async tasks to complete. You can use CountDownLatch (elegant solution which does NOT require an external library) to block the thread, until you receive response from server or timeout.
You can use CountDownLatch.
The await methods block until the current count reaches zero due to invocations of the countDown() method, after which all waiting threads are released and any subsequent invocations of await return immediately.
//Step 1: Do your background job
latch.countDown(); //Step 2 : On completion ; notify the count down latch that your async task is done
latch.await(); // Step 3: keep waiting
OR you can specify a timeout in your await call
try {
latch.await(2000, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
}
Sample Test Case
void testBackgroundJob() {
Latch latch = new CountDownLatch(1);
//Do your async job
Service.doSomething(new Callback() {
#Override
public void onResponse(){
ACTUAL_RESULT = SUCCESS;
latch.countDown(); // notify the count down latch
// assertEquals(..
}
});
//Wait for api response async
try {
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
assertEquals(expectedResult, ACTUAL_RESULT);
}
if already encapsulation retrofit2.0 with rx with restful
open class BaseEntity<E> : Serializable {
/*result code*/
var status: Int = 0
/**data */
var content: E? = null
}
and server api request like
#GET(api/url)
fun getData():Observable<BaseEntity<Bean>>
your service call back just one sync request Observable
val it = service.getData().blockingSingle()
assertTrue(it.status == SUCCESS_CODE)
As #Islam Salah said:
The JUnit framework never executes the code in the CallBack functions because the main thread of execution terminates before the response is retrieved.
You can use awaitility to solve the problem. Check out this answer on StackOverflow.