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());
Related
I'm using RxJava3 and Live data.
I'm calling methos getAllMovies and getAllGenres in background thread, then set the received data to MutableLiveData.
In my splash activity I'm calling these methods from viewModel.
I have lottie animation , and each time lottie animation end, I'm checking if data received , if yes , opening another activity, else, waiting again for data receive.
The problem is when I lose internet connection , data is not received after the Internet is recovering.
I'm trying to run app when the wifi and internet is off, and then turning on wifi.
But always getting 0 in methods size.
If I running the app with internet on , data received normally.
That what I have.
App repository:
private MutableLiveData<List<GenreResult>> mGenresResponseMutableLiveData = new MutableLiveData<>();
public MutableLiveData<List<GenreResult>> getGenresResponseMutableLiveData() {
AppService appService = RetrofitInstance.getService();
appService.getAllGenres(mApplication.getResources().getString(R.string.api_key),
mApplication.getResources().getString(R.string.language))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer<GenreResponse>() {
#Override
public void accept(GenreResponse genreResponses) throws Throwable {
mGenresResponseMutableLiveData.setValue(genreResponses.getGenreResults());
}
}, new Consumer<Throwable>() {
#Override
public void accept(Throwable throwable) throws Throwable {
Toast.makeText(mApplication.getApplicationContext(), throwable.getMessage(), Toast.LENGTH_LONG).show();
}
});
return mGenresResponseMutableLiveData;
}
ViewModel:
public class GenresViewModel extends AndroidViewModel {
private AppRepository mRepository;
public GenresViewModel(#NonNull Application application) {
super(application);
mRepository = new AppRepository(application);
}
public MutableLiveData<List<GenreResult>> getGenreLiveData() {
return mRepository.getGenresResponseMutableLiveData();
}
}
SplashActivity calling getGenre method:
private void getGenreList() {
mGenreResultArrayList = new ArrayList<>();
mGenresViewModel = new ViewModelProvider.AndroidViewModelFactory(getApplication()).create(GenresViewModel.class);
mGenresViewModel.getGenreLiveData().observe(this, new Observer<List<GenreResult>>() {
#Override
public void onChanged(List<GenreResult> genreResults) {
mGenreResultArrayList = (ArrayList<GenreResult>) genreResults;
}
});
}
SplashActivity checking on lottie listener if data received:
private void initViews() {
mLAVLoader = findViewById(R.id.lavLoader);
mLAVLoader.addAnimatorListener(new AnimatorListenerAdapter() {
#Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
if (mGenreResultArrayList != null && mGenreResultArrayList.size() > 0
&& mMovieResultArrayList != null && mMovieResultArrayList.size() > 0) {
Intent intent = new Intent(SplashActivity.this, MainActivity.class);
intent.putParcelableArrayListExtra("genreList", mGenreResultArrayList);
intent.putParcelableArrayListExtra("moviesList", mMovieResultArrayList);
startActivity(intent);
} else {
mLAVLoader.playAnimation();
Log.d("myDebug", "onAnimationEnd: " + mGenreResultArrayList.size()+ " "+mMovieResultArrayList.size());
}
}
});
}
I fixed it with retryWhen method in Repository:
public MutableLiveData<List<MovieResult>> getAllMoviesMutableLiveData() {
AppService appService = RetrofitInstance.getService();
appService.getAllMovies(mApplication.getResources().getString(R.string.api_key),
mApplication.getResources().getString(R.string.language),
"popularity.desc")
.retryWhen(throwable ->
throwable.delay(5, TimeUnit.SECONDS))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer<MoviesResponse>() {
#Override
public void accept(MoviesResponse moviesResponse) throws Throwable {
mAllMoviesMutableLiveData.setValue(moviesResponse.getMovieResults());
}
}, new Consumer<Throwable>() {
#Override
public void accept(Throwable throwable) throws Throwable {
}
});
return mAllMoviesMutableLiveData;
}
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.
I trying to get exchange rates from central bank. Unfortunately they don't have api, which can provide data in JSON. Only in XML. I'm using retrogit 2. I already created two classes, which describe xml, what I got from web site. But when I tried to get callback's response I got an 404 error code. Maybe my #GET method isn't correct? Please help me!
First XML fragment. it contains array of currencies on a date:
<ValCurs Date="14.01.2017" name="Foreign Currency Market">
<Valute ID="R01010">
<NumCode>036</NumCode>
<CharCode>AUD</CharCode>
<Nominal>1</Nominal>
<Name>Австралийский доллар</Name>
<Value>44,5156</Value>
</Valute>
And here is my interface:
public interface CbClient {
#GET("/XML_daily.asp")
Call<ValuteOnDate> getValuteOnDate();
}
And Service generator class:...
public class ServiceGenerator {
public static final String API_BASE_URL = "http://www.cbr.ru/scripts/";
private static OkHttpClient.Builder httpClient = new OkHttpClient.Builder();
private static Retrofit.Builder builder = new Retrofit.Builder()
.baseUrl(API_BASE_URL)
.addConverterFactory(SimpleXmlConverterFactory.create());
public static <S> S createService(Class<S> serviceClass) {
Retrofit retrofit = builder.client(httpClient.build()).build();
return retrofit.create(serviceClass);
}
}
And here is MainActivity class:...
public class MainActivity extends AppCompatActivity {
private static final String TAG = "TestRetrofitClien";
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
CbClient client = ServiceGenerator.createService(CbClient.class);
Call<ValuteOnDate> call = client.getValuteOnDate();
call.enqueue(new Callback<ValuteOnDate>() {
#Override
public void onResponse(Call<ValuteOnDate> call, Response<ValuteOnDate> response) {
try {
if (response.isSuccessful()) {
ValuteOnDate valuteOnDate = call.execute().body();
Log.i(TAG,"valuteOnDate: " + valuteOnDate);
ValuteOnDate valuteFromResponse = response.body();
Log.i(TAG,"valuteFromResponse: " + valuteFromResponse);
}else {
Log.e(TAG, "Retrofit Response: " + response.errorBody().string());
Log.d(TAG, "Error message: " + response.raw().message());
Log.d(TAG,"Error code: " + String.valueOf(response.raw().code()));
}
} catch (IOException e) {
Log.e("LOG", "Exeption: " + e);
}
}
#Override
public void onFailure(Call<ValuteOnDate> call, Throwable t) {
}
});
}
}
Remove the leading slash in #GET("/XML_daily.asp")
Each request to the server may return error_code. I want to handle these error in one place
when I was using AsyncTask I had a BaseAsyncTask like that
public abstract class BaseAsyncTask<Params, Progress, Result> extends AsyncTask<Params, Progress, Result> {
protected Context context;
private ProgressDialog progressDialog;
private Result result;
protected BaseAsyncTask(Context context, ProgressDialog progressDialog) {
this.context = context;
this.progressDialog = progressDialog;
}
#Override
protected void onPreExecute() {
super.onPreExecute();
}
#Override
protected void onPostExecute(Result result) {
super.onPostExecute(result);
HttpResponse<ErrorResponse> response = (HttpResponse<ErrorResponse>) result;
if(response.getData().getErrorCode() != -1) {
handleErrors(response.getData());
}else
onResult(result);
}
private void handleErrors(ErrorResponse errorResponse) {
}
public abstract void onResult(Result result);
}
But, using retrofit each request has its error handling callback:
git.getFeed(user,new Callback<gitmodel>() {
#Override
public void success(gitmodel gitmodel, Response response) {
}
#Override
public void failure(RetrofitError error) {
}
});
}
});
How can I handle all errors in one place?
If you need to get some 'logic' error, then you need some Java logic since it's not a Retrofit feature so basically:
Create a Your implementation Callback that implements the Retrofit Callback
Create a base object that define the method 'isError'
Modify Retrofit RestAdapter in order to get your Callback instead of the Retrofit One
MyCallback.java
import android.util.Log;
import retrofit.Callback;
import retrofit.client.Response;
public abstract class MyCallback<T extends MyObject> implements Callback<T> {
#Override
public final void success(T o, Response response) {
if (o.isError()) {
// [..do something with error]
handleLogicError(o);
}
else {
handleSuccess(o, response);
}
}
abstract void handleSuccess(T o, Response response);
void handleLogicError(T o) {
Log.v("TAG", "Error because userId is " + o.id);
}
}
MyObject.java (the base class for all your objects you get from Retrofit)
public class MyObject {
public long id;
public boolean isError() {
return id == 1;
}
}
MyRealObject.java - a class that extends the base object
public class MyRealObject extends MyObject {
public long userId;
public String title;
public String body;
}
RetroInterface.java - the interface used by retrofit you should be familiar with
import retrofit.http.GET;
import retrofit.http.Path;
public interface RetroInterface {
#GET("/posts/{id}")
void sendGet(#Path("id") int id, MyCallback<MyRealObject> callback);
}
And finally the piece of code where you use all the logic
RestAdapter adapter = new RestAdapter.Builder()
.setEndpoint("http://jsonplaceholder.typicode.com")
.build();
RetroInterface itf = adapter.create(RetroInterface.class);
itf.sendGet(2, new MyCallback<MyRealObject>() {
#Override
void handleSuccess(MyRealObject o, Response response) {
Log.v("TAG", "success");
}
#Override
public void failure(RetrofitError error) {
Log.v("TAG", "failure");
}
});
If you copy and paste this code, you'll get an error when you'll execute the itf.sendGet(1, new MyCallback..) and a success for itf.sendGet(2, new MyCallback...)
Not sure I understood it correctly, but you could create one Callback and pass it as a parameter to all of your requests.
Instead of:
git.getFeed(user,new Callback<gitmodel>() {
#Override
public void success(gitmodel gitmodel, Response response) {
}
#Override
public void failure(RetrofitError error) {
}
});
First define your Callback:
Callback<gitmodel> mCallback = new Callback<gitmodel>() {
#Override
public void success(gitmodel gitmodel, Response response) {
}
#Override
public void failure(RetrofitError error) {
// logic to handle error for all requests
}
};
Then:
git.getFeed(user, mCallback);
In Retrofit you can specify ErrorHandler to all requests.
public class ApiErrorHandler implements ErrorHandler {
#Override
public Throwable handleError(RetrofitError cause) {
//here place your logic for all errors
return cause;
}
}
Apply it to RestAdapter
RestAdapter.Builder()
.setClient(client)
.setEndpoint(endpoint)
.setErrorHandler(errorHandler)
.build();
I think that it is what you asked for.
In Retrofit2 you can't set an ErrorHandler with the method .setErrorHandler(), but you can create an interceptor to fork all possible errors centralised in one place of your application.
With this example you have one centralised place for your error handling with Retrofit2 and OkHttpClient. Just reuse the Retrofit object (retrofit).
You can try this standalone example with a custom interceptor for network and server errors. These both will be handled differently in Retrofit2, so you have to check the returned error code from the server over the response code (response.code()) and if the response was not successful (!response.isSuccessful()).
For the case that the user has no connection to the network or the server you have to catch an IOException of the method Response response = chain.proceed(chain.request()); and handle the network error in the catch block.
HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(loggingInterceptor)
.addInterceptor(new Interceptor() {
#Override
public Response intercept(Chain chain) throws IOException {
try {
Response response = chain.proceed(chain.request());
if (!response.isSuccessful()) {
Log.e("tag", "Failure central - response code: " + response.code());
Log.e("tag", "central server error handling");
// Central error handling for error responses here:
// e.g. 4XX and 5XX errors
switch (response.code()) {
case 401:
// do something when 401 Unauthorized happened
// e.g. delete credentials and forward to login screen
// ...
break;
case 403:
// do something when 403 Forbidden happened
// e.g. delete credentials and forward to login screen
// ...
break;
default:
Log.e("tag", "Log error or do something else with error code:" + response.code());
break;
}
}
return response;
} catch (IOException e) {
// Central error handling for network errors here:
// e.g. no connection to internet / to server
Log.e("tag", e.getMessage(), e);
Log.e("tag", "central network error handling");
throw e;
}
}
})
.build();
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("http://10.0.2.2:8000/api/v1/")
.client(client)
.addConverterFactory(GsonConverterFactory.create())
.build();
UserRepository backendRepository = retrofit.create(UserRepository.class);
backendRepository.getUser("userId123").enqueue(new Callback<UserModel>() {
#Override
public void onResponse(Call<UserModel> call, retrofit2.Response<UserModel> response) {
Log.d("tag", "onResponse");
if (!response.isSuccessful()) {
Log.e("tag", "onFailure local server error handling code:" + response.code());
} else {
// its all fine with the request
}
}
#Override
public void onFailure(Call<UserModel> call, Throwable t) {
Log.e("tag", "onFailure local network error handling");
Log.e("tag", t.getMessage(), t);
}
});
UserRepository example:
public interface UserRepository {
#GET("users/{userId}/")
Call<UserModel> getUser(#Path("userId") String userId);
}
UserModel example:
public class UserModel implements Parcelable {
#SerializedName("id")
#Expose
public String id = "";
#SerializedName("email")
#Expose
public String mail = "";
public UserModel() {
}
protected UserModel(Parcel in) {
id = in.readString();
mail = in.readString();
}
public static final Creator<UserModel> CREATOR = new Creator<UserModel>() {
#Override
public UserModel createFromParcel(Parcel in) {
return new UserModel(in);
}
#Override
public UserModel[] newArray(int size) {
return new UserModel[size];
}
};
#Override
public int describeContents() {
return 0;
}
#Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(id);
dest.writeString(mail);
}
}
Fairly simply Retrofit custom error handling example. Is set up so that you don't need to do much work in the 'failure' handler of a retrofit call to get the user-visible error message to show. Works on all endpoints. There's lots of exception handling as our server folks like to keep us on our toes by sending all kinds of random stuff..!
// on error the server sends JSON
/*
{ "error": { "data": { "message":"A thing went wrong" } } }
*/
// create model classes..
public class ErrorResponse {
Error error;
public static class Error {
Data data;
public static class Data {
String message;
}
}
}
//
/**
* Converts the complex error structure into a single string you can get with error.getLocalizedMessage() in Retrofit error handlers.
* Also deals with there being no network available
*
* Uses a few string IDs for user-visible error messages
*/
private static class CustomErrorHandler implements ErrorHandler {
private final Context ctx;
public CustomErrorHandler(Context ctx) {
this.ctx = ctx;
}
#Override
public Throwable handleError(RetrofitError cause) {
String errorDescription;
if (cause.isNetworkError()) {
errorDescription = ctx.getString(R.string.error_network);
} else {
if (cause.getResponse() == null) {
errorDescription = ctx.getString(R.string.error_no_response);
} else {
// Error message handling - return a simple error to Retrofit handlers..
try {
ErrorResponse errorResponse = (ErrorResponse) cause.getBodyAs(ErrorResponse.class);
errorDescription = errorResponse.error.data.message;
} catch (Exception ex) {
try {
errorDescription = ctx.getString(R.string.error_network_http_error, cause.getResponse().getStatus());
} catch (Exception ex2) {
Log.e(TAG, "handleError: " + ex2.getLocalizedMessage());
errorDescription = ctx.getString(R.string.error_unknown);
}
}
}
}
return new Exception(errorDescription);
}
}
// When creating the Server...
retrofit.RestAdapter restAdapter = new retrofit.RestAdapter.Builder()
.setEndpoint(apiUrl)
.setLogLevel(retrofit.RestAdapter.LogLevel.FULL)
.setErrorHandler(new CustomErrorHandler(ctx)) // use error handler..
.build();
server = restAdapter.create(Server.class);
// Now when calling server methods, get simple error out like this:
server.postSignIn(login,new Callback<HomePageResponse>(){
#Override
public void success(HomePageResponse homePageResponse,Response response){
// Do success things!
}
#Override
public void failure(RetrofitError error){
error.getLocalizedMessage(); // <-- this is the message to show to user.
}
});
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.