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.
Related
Here is my current code. The problem with this code is I need to wait get the data sequentially. The loading time is poor because of this. I want to use something like .enqueue() to get asynchronously several data at once, but I want to wait until I get all the data before continuing the process. Is it possible to do it with Retrofit?
List<Data> datas = new ArrayList<>();
for (long dataId : mDataIds) {
Response<T> response = resource.getData(dataId).execute();
if (response.isSuccessful()) {
datas.add(data.body());
}
}
//do something else
You can solve this problem very elegantly using RxJava.
If you never heard of RxJava before, it is a solution to many of your problems.
If you don't use java8 or retrolambda I recommend you to start using it, as it makes working with RxJava a piece of cake.
Anyway here's what you need to do:
// 1. Stream each value from mDataIds
Observable.from(mDataIds)
// 2. Create a network request for each of the data ids
.flatMap(dataId -> resource.getData(dataId))
// 3. Collect responses to list
.toList()
// Your data is ready
.subscribe(datas -> {}, throwable -> {});
1) First add RxJava2 dependencies to your project
2) Define retrofit api interface methods which return RxJava observable types
public interface DataApi {
#GET("dataById/")
Observable<Data> getData(#Query("id") String id);
}
3) Call api passing input data like below.
Observable.fromIterable(idList).subscribeOn(Schedulers.computation())
.flatMap(id -> {
return retrofitService.getData(id).subscribeOn(Schedulers.io());
}).toList().
.observeOn(AndroidSchedulers.mainThread()).subscribe( listOfData -> {// do further processing }, error -> { //print errors} );
For reference : http://www.zoftino.com/retrofit-rxjava-android-example
Define interface with callback Model type.
public interface LoginService {
#GET("/login")
Call<List<Login>> getLogin();
}
In you calling method override the callback method.
LoginService loginService = ServiceGenerator.createService(LoginService.class);
Call<List<Login>> call = loginService.getLogin();
call.enqueue(new Callback<List<Login>>() {
#Override
public void onResponse(Call<List<Login>> call, Response<List<Login>> response) {
if (response.isSuccessful()) {
// Login successful
} else {
// error response, no access to resource?
}
}
#Override
public void onFailure(Call<List<Login>> call, Throwable t) {
// something went completely south (like no internet connection)
Log.d("Error", t.getMessage());
}
}
I would recommend using RxJava and try it. You have something called FlatMap to combine the results.
To Start here is the tutorial start for RxJava2 and Retrofit2.
I am new at RxJava and I have some pain to execute my first 'difficult' query.
I have two Observables generated from Retrofit, one that 'ping' a new api, the other the old one. The first one will query 'http://myurl.com/newapi/ping', the second one 'http://myurl.com/oldapi/ping'. Result from this request doesn't matter, I just want to know if the server is using the new or old api.
So I would like to call both observables at the same time, and finally have a boolean at the end to know if I'm using old or new api.
I tried something like that
Observable.mergeDelayError(obsOldApi,obsNewApi)
.observeOn(AndroidSchedulers.mainThread(), true)
.subscribeOn(Schedulers.io())
.subscribe(new Subscriber<String>() {
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable e) {
}
#Override
public void onNext(String s) {
}
});
But onError will be called once (I would like it to be called only if both request failed) and when onNext is called, I don't know from which request it came (old or new api ?).
Thank you for you help
For simplicity, let say that you'll received "NEW" or "OLD" regarding which api is available.
The difficulty of your operation is to manage errors : RxJava deals errors as terminal state. So you'll have to ignore this error, using .onErrorResumeNext() for example.
Observable<String> theOld = oldApi.map(r -> "OLD")
// ignore errors
.onErrorResumeNext(Obervable.empty());
Observable<String> theNew = newApi.map(r -> "NEW")
.onErrorResumeNext(Obervable.empty());
Observable.merge(theOld, theNew)
.first() // if both api are in errors
.subscribe(api -> System.out.println("Available API : "+api));
I added the operator first : it will take only the first result ("OLD" or "NEW") but trigger an error if the previous Observable is empty, which is the case if both API are unavaible.
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.
So currently, Im making an async network request with a date parameter (using Retrofit), and if that request returns with a response code that isnt 200 (or if its 429, 400, or if the response body is just null, whatever is easiest to determine), I make a new request with a date parameter 1 day earlier. Again, if this request comes back with a response code that isnt 200, I make one more request with a date 1 day earlier than the previous, for a total of 3 possible requests if the first two fail.
I'm currently achieving this with a bunch of callbacks and calling a new method set up to perform the request with the day -1 for each try.
I know that I can achieve a cleaner solution with Rx and Retrofits built in Rx features. How would this be done?
First you need to add RxAndroid and RxJava adapter in your dependencies
compile 'com.squareup.retrofit:adapter-rxjava:2.0.0-beta2'
compile 'io.reactivex:rxandroid:1.0.1'
and then you need to register the call adapter to your Retrofit declaration
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(myBaseurl)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.build();
And then you can change your service interface return type from Call to Observable
public interface MyAPIService {
#POST("user")
Call<User> getUser();
#POST("user")
Observable<Response<User>> getUserWithRxJava();
#POST("user_friends")
Observable<Response<List<User>>> getUserFriends();
}
And this is an example for chaining call
myService.getUserWithRxJava()
.observeOn(AndroidSchedulers.mainThread())
.flatMap(new Func1<Response<User>, Observable<Response<List<User>>>>() {
#Override
public Observable<List<Home>> call(Response<User> responseUser) {
// You can also use responseUser.code to get the response code
// but isSuccess() function will return true if the code
// is in the range [200..300)
if (responseUser.isSuccess()) {
return myService.getUserFriends();
} else {
// You can also use Observable.empty() if you want to ignore unsuccessful response
return Observable.error(myThrowable);
}
}
})
.subscribe(new Subscriber<Response<List<User>>>() {
#Override
public void onCompleted() {
// TODO Completed
}
#Override
public void onError(Throwable e) {
// TODO Handle the error
}
#Override
public void onNext(Response<List<User>> friendListResponse) {
// TODO do something with the data
// To get the serialized data you can use friendListResponse.body();
}
});
The non-200 responses should come back as RetrofitError objects, which contain Response objects with a status code.
You could do something like this:
observable
.retryWhen(new RetryStrategy())
.subscribe(...);
and RetryStrategy might look like this (note, I'm using retrolambda, so wherever you see -> just replace with a new anonymous inner class):
public class RetryStrategy implements Func1<Observable<? extends Throwable>, Observable<?>> {
public RetryStrategy() {}
#Override
public Observable<?> call(Observable<? extends Throwable> attempts) {
return attempts.flatMap((throwable) -> {
if (throwable instanceof RetrofitError) {
RetrofitError error = (RetrofitError) throwable;
if (error.getKind() == RetrofitError.Kind.HTTP) {
if (error.getResponse().getStatus() == 401) {
// This is where you attempt to recover
return someRecoveryObservable???;
}
}
}
// Bubble any other errors back up, e.g. connection loss.
return Observable.error(throwable);
});
}
}
You could also implement Exponential Backoff here by adding support for a retry count, along with using Observable.timer(long, TimeUnit) in your recovery phase (and compute the timing based on the current number of tries). Spotty connection problems sometimes benefit greatly from this approach - especially with fire & forget tasks that run in the background.
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);
}