I'm trying to create unitest case for retrofit 2 callbacks in android. I use for test mockito, MockWebServer and MockResponse.
public class LoginFragment extends Fragment {
/**
* Actualiza el numero telefonico para el usuario
*
* #param phoneNumber
*/
public void phoneNumber(String phoneNumber) {
HttpService service = Service.createService(HttpService.class, TOKEN);
Call<Void> call = service.phonumber(phoneNumber, new User("", ""));
call.enqueue(callback());
}
/**
* #return Callback<Void>
*/
public Callback<Void> callback() {
return new Callback<Void>() {
#Override
public void onResponse(Call<Void> call, Response<Void> response) {
if (response.isSuccessful()) {
dummy();
} else {
Log.e(TAG, "problema");
}
}
#Override
public void onFailure(Call<Void> call, Throwable t) {
Log.e(TAG, " " + t);
}
};
}
public void dummy(){
System.out.println(" called");
}
}
My unitest class:
#RunWith(MockitoJUnitRunner.class)
public class TestLoginFragment {
MockWebServer mockWebServer;
#Before
public void setup() throws Exception {
spyLoginFragment = mock(LoginFragment.class);
mockWebServer = new MockWebServer();
}
#Test
public void testDummyIsCalled() {
spyLoginFragment.phoneNumber("3333335");
mockWebServer.enqueue(new MockResponse().setResponseCode(201));
verify(spyLoginFragment, times(1)).dummy();
}
}
But when you run the test I get:
TestLoginFragment > testDummyIsCalled FAILED
Wanted but not invoked:
loginFragment.dummy();
I'm new making callback test, how can I verify that dummy() was called?
By definition, unit test only tests the functionality of the units themselves. Therefore, it may not catch integration errors.
You shouldn´t test the retrofit framework or its callbacks, you must be presumed that retrofit always works. Only Test your code, so create a test for phoneNumber(String phoneNumber) that checks if the service was configured correctly (not need to launch retrofit service), and create others test to check the posible responses from the server in OnSuccess or OnFailure cases.
PD: If you want to test the coupling between the Retrofit call and the callback's methods, then you're talking about "integration test".
Related
I am using .aar library in my app.
It has one Interface for the Network delegation which I need to overwrite.
ApiResponse executeApiCall(String url, HTTPMethod method, String params)
I am using Retrofit for the network calls. I need to convert the Synchronous call to USE asynchronous.
#Override
public ApiResponse executeApiCall(String url, HTTPMethod method, String params) {
ApiResponse apiResponse;
try {
// Synchronous Call
Call<String> call = RestClient.get().getStringResponse(url, method, params);
Response<String> response = call.execute();
apiResponse = new ApiResponse(response.code(), response.body());
} catch (Exception e) {
apiResponse = new ApiResponse();
}
return apiResponse;
}
Now I am stuck in how to use Asynchronous call within the Network Interface I must overwrite.
#Override
public ApiResponse executeApiCall(String url, HTTPMethod method, String params) {
ApiResponse apiResponse;
// Asynchronous Call
Call<String> call = RestClient.get().getStringResponse(url, method, params);
call.enqueue(new Callback<String>() {
#Override
public void onResponse(#NotNull Call<String> call, #NotNull Response<String> response) {
if (response.isSuccessful()) {
apiResponse = new ApiResponse(response.code(), response.body());
}
}
#Override
public void onFailure(#NotNull Call<String> call, #NotNull Throwable t) {
apiResponse = new ApiResponse();
}
});
return apiResponse;
}
I can not change the Network delegation interface. I must overwrite it and I need to use retrofit Asynchronous.
Your feedback is most appreciated. Thanks guys.
I found some code on a different stack overflow thread. Here is how you could use the code to achieve what you are looking for.
Here is the class
public abstract class AsyncRunnable<T> {
protected abstract void run(AtomicReference<T> notifier);
protected final void finish(AtomicReference<T> notifier, T result) {
synchronized (notifier) {
notifier.set(result);
notifier.notify();
}
}
public static <T> T wait(AsyncRunnable<T> runnable) {
final AtomicReference<T> notifier = new AtomicReference<>();
// run the asynchronous code
runnable.run(notifier);
// wait for the asynchronous code to finish
synchronized (notifier) {
while (notifier.get() == null) {
try {
notifier.wait();
} catch (InterruptedException ignore) {}
}
}
// return the result of the asynchronous code
return notifier.get();
}
}
You can use it like this
ApiResponse result = AsyncRunnable.wait(new AsyncRunnable<ApiResponse>() {
#Override
public void run(final AtomicReference<String> notifier) {
// here goes your async code, e.g.:
new Thread(new Runnable() {
#Override
public void run() {
Call<String> call =
RestClient.get().getStringResponse(url, method, params);
call.enqueue(new Callback<String>() {
#Override
public void onResponse(#NotNull Call<String> call,
#NotNull
Response<String> response) {
if (response.isSuccessful()) {
apiResponse = new
ApiResponse(response.code(),
response.body());
finish(notifier, apiResponse);
}
}
#Override
public void onFailure(#NotNull Call<String> call, #NotNull Throwable t) {
apiResponse = new ApiResponse();
finish(notifier, apiResponse);
}
});
}
}).start();
}
});
We wait for the response to come from the callback and notify the AsyncRunnable class.
I feel you, I had a similar problem with a library that needed to do heavy work (non-UI) on the UI thread. Yep, sounds like a psycopath's lib. Coroutines runBlocking are your friend. I realize that you're asking for a rx-java solution, but can't beat coroutines for this.
/**
* You can edit, run, and share this code.
* play.kotlinlang.org
*/
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking
fun main() {
val apiResponse = executeApiCall()
println(apiResponse) // prints "response"
println("goodbye, world!!!")
}
/**
* Synchronous function that waits for coroutine to finish
*/
fun executeApiCall():String {
return runBlocking(Dispatchers.IO){
retrofitCall()
}
}
/**
* Method that invokes Retrofit call in the background.
*/
suspend fun retrofitCall():String{
// RestClient.get() ...
return "response"
}
Note that if you call executeApiCall from UI thread you still will block the UI thread. So possible you need something like lifecycleScope.launch{executeApiCall() } if you're inside an activity or fragment (gradle dependency androidx.lifecycle:lifecycle-runtime-ktx:2.1.0), or CoroutineScope(Dispatchers.IO).launch { executeApiCall() } otherwise.
I have this method that I am trying to pull data from an API, and then update the text view. Everything works except getRecipeName doesn't finish after the "end Method" log. .getRecipeName() uses RetroFit to pull from an API.
I am currently learning MVP, Dagger, RxJava, and Butterknife all at once using
Mindork's Github page on MVP Architecture
I commented out the .subscribeOn and .observeOn to see the result difference and nothing changed.
#Override
public void onRandomButtonClicked() {
getMvpView().showLoading();
Log.e(TAG, "Random Method Open");
getCompositeDisposable().add(getDataManager()
.getRecipeName()
//.subscribeOn(getSchedulerProvider().io())
//.observeOn(getSchedulerProvider().ui())
.subscribe(new Consumer<String>() {
#Override
public void accept(String s) throws Exception {
Log.e(TAG, "accept");
getMvpView().updateTextView(title);
}
}));
Log.e(TAG, "end method");
}
Here is my getRecipeName() method
#Override
public Observable<String> getRecipeName() {
/*Create handle for the RetrofitInstance interface*/
GetDataService service = RetrofitClientInstance.getRetrofitInstance().create(GetDataService.class);
Call<RecipeList> call = service.getRecipe();
call.enqueue(new Callback<RecipeList>() {
#Override
public void onResponse(#NonNull Call<RecipeList> call, #NonNull retrofit2.Response<RecipeList> response) {
Log.e("onResponse","Recipe is Successful = " + response.isSuccessful());
//if response is false then skip to avoid null object reference
if (response.isSuccessful()) {
RecipeList drinkRecipe = response.body();
List<Recipe> recipes = drinkRecipe.getDrinks();
jokeText = String.valueOf(recipes.size());
Recipe myRecipe = recipes.get(0);
jokeText = myRecipe.getStrDrink();
Log.e("On Response", "Result2: " + jokeText);
}
//jokeText = "null";
}
#Override
public void onFailure(Call<RecipeList> call, Throwable t) {
Log.e("On Response","Failure");
}
});
//return jokeText;
return Observable.fromCallable(new Callable<String>() {
#Override
public String call() throws Exception {
return jokeText;
}
});
}
Solution
So as the comments stated RxJava Adapter was the correct way to go. I will just post my working code on myself using the adapter. I found it very difficult to find a working example.
//single api call using retrofit and rxjava
#SuppressLint("CheckResult")
private void getRandomButtonClick(){
retrofit = RetrofitClientInstance.getRetrofitInstance();
retrofit.create(GetDataService.class).getRecipe()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(this::handleResults, this::handleError );
}
private void handleResults(RecipeList recipeList) {
int i = recipeList.getDrinks().size();
Log.e(TAG, "size is: "+ i);
Recipe recipe = recipeList.getDrinks().get(0);
getMvpView().updateTextView(recipe.getStrDrink());
}
private void handleError(Throwable t){
Log.e("Observer", "");
}
My Retrofit Client Instance
public static Retrofit getRetrofitInstance() {
if (retrofit == null) {
retrofit = new retrofit2.Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build();
}
return retrofit;
}
My Interface
public interface GetDataService {
//#Headers({})
#GET("random.php")
Observable<RecipeList> getRecipe();
I found a great resource to reference for me to correctly implement this. Retrofit Android
The reason is because your observable is returning jokeText every time it is subscribed upon. It returns immediately after invocation and will not wait for your network operation.
One possible solution is to use the RxJavaCallAdapter. Link here: https://github.com/square/retrofit/tree/master/retrofit-adapters/rxjava2
It will automatically convert your API returns to observables. No need to manually invoke retrofit requests. Just process the response and convert it to your desired object from there.
Another approach would be to wrap your entire sequence in an Observable.create or Observable.fromAsync.
I have following Service interface for retrofit :
public interface TimetableService {
#GET("url/")
Call<Flixbus> getTimetable();
}
I want to test UI after retrofit call :
#RunWith(AndroidJUnit4.class)
public class TestTimetablePresenter {
private static List<Departure> DEPARTURES = Lists.newArrayList(
new Departure("L006", "Stockholm",
new Datetime(1461056400, "GMT+02:00")),
new Departure("L007", "Berlin",
new Datetime(1461056500, "GMT+02:00")));
#Mock
private TimetableContract.View mTimetableView;
#Mock
private TimetableService mockTimetableServiceImpl;
#Captor
private ArgumentCaptor<Callback<Flixbus>> mCallbackArgumentCaptor;
private TimetablePresenter mNotesPresenter;
#Before
public void setupNotesPresenter() {
MockitoAnnotations.initMocks(this);
SampleApp mApp = (SampleApp) InstrumentationRegistry.getInstrumentation()
.getTargetContext().getApplicationContext();
mNotesPresenter = new TimetablePresenter(mApp, mTimetableView);
mNotesPresenter.setWebService(mockTimetableServiceImpl);
}
#Test
public void loadDeparturesFromAPIAndLoadIntoView() {
mNotesPresenter.loadDepartures();
Mockito.verify(mockTimetableServiceImpl).getTimetable().enqueue(mCallbackArgumentCaptor.capture());
InOrder inOrder = Mockito.inOrder(mTimetableView);
inOrder.verify(mTimetableView).setProgressIndicator(true);
inOrder.verify(mTimetableView).setProgressIndicator(false);
Mockito.verify(mTimetableView).showDepartures(DEPARTURES);
}
}
But at this line, I get an error:
Mockito.verify(mockTimetableServiceImpl).getTimetable().enqueue(mCallbackArgumentCaptor.capture());
Error says:
java.lang.NullPointerException: Attempt to invoke interface method 'retrofit2.Call retrofit2.Call.clone()' on a null object reference at com.ali.android.sample.timetable.TimetablePresenter.loadDepartures(TimetablePresenter.java:46)
I can't figure out how to solve it. Do you have any suggestion?
#Override
public void loadDepartures() {
mTimetableView.setProgressIndicator(true);
service.getTimetable().clone().enqueue(new Callback<Flixbus>() {
#Override
public void onResponse(Call<Flixbus> call, Response<Flixbus> response) {
Log.d(TAG, "response is successful :" + response.isSuccessful());
mTimetableView.setProgressIndicator(false);
if(response.isSuccessful()) {
mTimetableView.showDepartures(response.body().getTimetable().getDepartures());
} else {
Log.d(TAG, "Response Code: " + response.code() + "Message: " + response.message());
}
}
#Override
public void onFailure(Call<Flixbus> call, Throwable t) {
mTimetableView.setProgressIndicator(false);
Log.e(TAG, t.getLocalizedMessage());
}
});
}
Have you tried switching the order of verification? Usually mock verification comes after interaction with class under test.
// I believe TimetableService#getTimetable().enqueue(...) is called inside TimetablePresenter#loadDepartures()
mNotesPresenter.loadDepartures();
Mockito.verify(mockTimetableServiceImpl).getTimetable().enqueue(mCallbackArgumentCaptor.capture());
I am building an activity in which I'm loading lists of objects from an api. I need to make multiple requests with retrofit which returns different objects. I can make the requests but I don't know how I can check when they're done.
The following code is what I have.
ApiRepository
public interface ApiRepository {
#GET("/api/troopmarker.json")
Call<List<TroopMarker>> getTroopMarkers();
#GET("/api/troop.json")
Call<List<Troop>> getTroops();
#GET("/api/treasure.json")
Call<List<TroopMarker>> getTreasures();
}
RepositoryService
public interface RepositoryService
{
void loadTroops(final TroopCallback callback);
void loadTroopMarkers(final TroopMarkerCallback callback);
//void loadTreasures(final TreasureCallback callback);
}
RepositoryServiceImpl
public class RepositoryServiceImpl implements RepositoryService {
private String url;
private Activity context;
public RepositoryServiceImpl(String url, Activity context) {
this.url = url;
this.context = context;
}
public void loadTroops(final TroopCallback callback) {
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(url)
.addConverterFactory(GsonConverterFactory.create())
.build();
ApiRepository repository = retrofit.create(ApiRepository.class);
repository.getTroops().enqueue(new Callback<List<Troop>>() {
public List<Troop> troops;
#Override
public void onResponse(Call<List<Troop>> call, Response<List<Troop>> response) {
if(response.isSuccessful()) {
Log.d("RETROFIT", "RESPONSE " + response.body().size());
callback.onSuccess(response.body());
}
}
#Override
public void onFailure(Call<List<Troop>> call, Throwable t) {
CharSequence text = "Error loading troops.";
int duration = Toast.LENGTH_LONG;
Toast toast = Toast.makeText(context, text, duration);
toast.show();
callback.onSuccess(null);
}
});
}
public void loadTroopMarkers(final TroopMarkerCallback callback) {
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(url)
.addConverterFactory(GsonConverterFactory.create())
.build();
ApiRepository repository = retrofit.create(ApiRepository.class);
repository.getTroopMarkers().enqueue(new Callback<List<TroopMarker>>() {
#Override
public void onResponse(Call<List<TroopMarker>> call, Response<List<TroopMarker>> response) {
if(response.isSuccessful()) {
Log.d("RETROFIT", "RESPONSE " + response.body().size());
callback.onSuccess(response.body());
}
}
#Override
public void onFailure(Call<List<TroopMarker>> call, Throwable t) {
CharSequence text = "Error loading troops.";
int duration = Toast.LENGTH_LONG;
Toast toast = Toast.makeText(context, text, duration);
toast.show();
callback.onSuccess(null);
}
});
}
public void loadTreasures() {
}
}
LoadActivity
public class LoadActivity extends AppCompatActivity
{
//TODO LOAD TROOPS AND TROOPMARKERS
//Load troops, troopmarkers, treasures and put on map
public List<Troop> troops;
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_loading);
//Start RepositoryService
final RepositoryService repositoryService = new RepositoryServiceImpl("http://internco.eu", this);
//Load troops
repositoryService.loadTroops(new TroopCallback() {
#Override
public void onSuccess(List<Troop> troops) {
Log.d("RETROFIT", "SUCCESFULLY LOADED TROOPS SIZE: " + troops.size());
}
});
//Load troopMarkers
repositoryService.loadTroopMarkers(new TroopMarkerCallback() {
public List<TroopMarker> troopMarkers;
#Override
public void onSuccess(List<TroopMarker> troopMarkers) {
Log.d("RETROFIT", "SUCCESFULLY LOADED TROOPMARKERS SIZE: " + troopMarkers.size());
}
});
//Should now here when I'm done with my requests.
Log.d("RETROFIT", "DONE");
}
}
Can someone point me out on this? I think that I have to use the RxJava library but I can't figure this out.
Your help is much appreciated.
1 hacky way of doing it would be to keep 2 flag variables loadTroopsflag & loadTroopMarkersflag.Then in the onSuccess callbacks of each check whether both are true and if they are then both your requests are complete. There might be edge cases in implementing a workaround like this but it should generally work. In case your requests depend on each other then as you will need to use nested called ie,
repositoryService.loadTroops(new TroopCallback() {
#Override
public void onSuccess(List<Troop> troops) {
Log.d("RETROFIT", "SUCCESFULLY LOADED TROOPS SIZE: " + troops.size());
repositoryService.loadTroopMarkers(new TroopMarkerCallback() {
public List<TroopMarker> troopMarkers;
#Override
public void onSuccess(List<TroopMarker> troopMarkers) {
Log.d("RETROFIT", "SUCCESFULLY LOADED TROOPMARKERS SIZE: " + troopMarkers.size());
}
});
}
});
Something like that,so in case you have more dependencies then your nested callbacks increase which is where Rxjava would come in and solve it in a few lines of code.I don't think you need to jump into Rx just yet as this is a relatively small problem and you Rx java brings in extra space that would increase the size of the app as well as development time.
Also note the part where you mention
//Should now here when I'm done with my requests.
Log.d("RETROFIT", "DONE");
does not imply that the requests are done,it simply means that they are queued up and in progress.These are asynchronous request and will complete when the callback completes.
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);