Running test with MockWebServer always call the failure call back (Connection Exception) - android

I'm running mock web server to test REST API calls, when activity launch and start the mockWebserver and execute the API call I'm getting the connection refused,using OKHTTP v-3.10.0 & Retrofit 2.3.0
ActivityTest Code:
#Rule
public ActivityTestRule<STBUpgradeActivity> mActivityRule = new ActivityTestRule<>(STBUpgradeActivity.class, false, false);
private MockWebServer server;
#Before
public void setUp() throws Exception {
super.setUp();
server = new MockWebServer();
server.start();
injectInstrumentation(InstrumentationRegistry.getInstrumentation());
String serverUrl = server.url("/").toString();
EnvConfig.WEB_API_SIT = serverUrl;
}
#Test
public void testPageContentIsShown() throws Exception {
String fileName = "stb_upgrade_page_content_200.json";
server.enqueue(new MockResponse()
.setResponseCode(200)
.setBody(RestServiceTestHelper.getStringFromFile(getInstrumentation().getContext(), fileName)));
Intent intent = new Intent();
mActivityRule.launchActivity(intent);
onView(withId(R.id.button_upgrade)).check(matches(isDisplayed()));
onView(withText("New residential subscribers and standard install only. If Sports is a part of your package")).check(matches(isDisplayed()));
}
=======
RestAPI client
private Retrofit getClient() {
retrofit = new Retrofit.Builder()
.baseUrl(WEB_API_HOST)
.addConverterFactory(GsonConverterFactory.create())
.client(getHttpClient())
.build();
return retrofit;
}

Change the setUp code to
#Before
public void setUp() throws Exception {
super.setUp();
server = new MockWebServer();
server.start(8080);
injectInstrumentation(InstrumentationRegistry.getInstrumentation());
}
set the Retroft Base URL to
private Retrofit getClient() {
retrofit = new Retrofit.Builder()
.baseUrl("http://localhost:8080/")
.addConverterFactory(GsonConverterFactory.create())
.client(getHttpClient())
.build();
return retrofit;
}

Related

How to stop Retrofit from caching old parameter values?

On some APIs on the app I'm working on, the user location is sent as one of the parameters, so their locations can be monitored via web dashboard, like this:
#FormUrlEncoded
#POST("v1/visit")
Call<StartVisitResponse> startVisit(
#Header("Authorization") String token,
#Header("latitude") String latiude,
#Header("longitude") String longitude,
#Field("outlet_id") int outlet_id,
#Field("date") String String visit_date
);
And this is an activity that illustrates the main problem:
class StartVisitActivity implements AppCompactActivity {
ActivityStartVisitBinding uiBinding;
InstantLocate theInstantLocate; // from https://github.com/mukul56/InstantLocate
#Override
protected void onCreate(Bundle savedInstanceState) {
uiBinding.btnGetLocation( v -> {
initLocation();
});
uiBinding.btnStartVisit( v -> {
ApiClient.getClient().create(ApiInterface.class).startVisit(
Helper.getAuthToken(),
""+theInstantLocate.getlatitude(),
""+theInstantLocate.getlongitude(),
outletId,
date).enqueue(new Callback<StartVisitResponse>() {
#Override
public void onResponse(Call<StartVisitResponse> call, Response<StartVisitResponse> response) {
if (response.isSuccessful()) {
} else {
}
}
#Override
public void onFailure(Call<StartVisitResponse> call, Throwable t) {
}
});
})
}
private void initLocation(){
theInstantLocate = new InstantLocate(this);
theInstantLocate.instantLocation();
Toast.makeText(getApplicationContext, "Hi you are going to visit outlet ("+theInstantLocate.getlatitude()+","+theInstantLocate.getlongitude()+").", Toast.LENGTH_SHOW).show();
}
}
First press the "Get Location" button to get our current location, then the "Start Visit" button. While inspecting the web dashboard, we found some users visiting different outlets started at the same position (which actually didn't). Weird.
After about 15 minutes of changing FakeGPS location and calling initLocation() (without quitting the app) repeatedly, both locations are always match. The issue is on startVisit(). For example I changed locations a few time: Tokyo -> Bangkok -> Jakarta. When inspecting the startVisit log, my detection location was still in Tokyo. Seems like Retrofit cached my old position parameters. How to clear those, so the latest values are always used?
For more information, this is my ApiClient class:
public class ApiClient {
public static final String BASE_URL = ".........";
private static Retrofit retrofit = null;
public static Retrofit getClient() {
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
Interceptor httpClient = chain -> {
Request original = chain.request();
String method = original.method();
HttpUrl.Builder httpBuilder = original.url().newBuilder();
String token = Prefs.getString("TOKEN", null);
Request.Builder requestBuilder;
if (token != null){
requestBuilder = original.newBuilder()
.header("Accept", "application/json")
.header("Content-Type", "application/json")
.method(method, original.body())
.header("Authorization", Helper.getToken())
.header("latitude", Helper.getLatitude())
.header("longitude", Helper.getLongitude());
}else {
requestBuilder = original.newBuilder()
.header("Accept", "application/json")
.header("Content-Type", "application/json")
.method(method, original.body());
}
Request request = requestBuilder.url(httpBuilder.build()).build();
return chain.proceed(request);
};
OkHttpClient client = null;
client = new OkHttpClient.Builder().connectTimeout(100, TimeUnit.SECONDS)
.readTimeout(100, TimeUnit.SECONDS).addInterceptor(interceptor)
.addInterceptor(new ChuckerInterceptor(BaseApp.getAppContext()))
.addInterceptor(httpClient).build();
if (retrofit==null) {
retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.client(client)
.addConverterFactory(GsonConverterFactory.create())
.build();
}
return retrofit;
}
}

Test running failed: Instrumentation run failed due to 'android.os.NetworkOnMainThreadException'

I've started working with Espresso UI tests. I prepare custom MockTestRunner, MockApplication for initialization Dagger components and I've defined mock modules too. It looks like that:
public class MockTestRunner extends AndroidJUnitRunner {
public Application newApplication(ClassLoader cl, String className, Context context)
throws InstantiationException, IllegalAccessException, ClassNotFoundException {
return super.newApplication(cl, MockMyApplication.class.getName(), context);
} }
MyApp is extended by
public class MockQrApplication extends MyApp {
private MockWebServer mockWebServer;
protected void initComponent() {
mockWebServer = new MockWebServer();
component = DaggerMyAppComponent
.builder()
.myAppModule(new MyAppModule(this))
.busModule(new BusModule())
.apiModule(new MockApiModule(mockWebServer))
.facebookModule(new FacebookModule())
.dataManagerModule(new DataManagerModule())
.greenDaoModule(new GreenDaoModule())
.trackModule(new TrackModule(this))
.build();
component.inject(this);
}
}
I added testInstrumentationRunner into gradle
defaultConfig {
....
multiDexEnabled true
testInstrumentationRunner "a.b.c.MockTestRunner"
}
I want run login tests in my LoginActivity
#RunWith(AndroidJUnit4.class)
#LargeTest
public class LoginActivityTest {
protected Solo solo;
#Rule
public ActivityTestRule<LoginActivity> activityTestRule = new ActivityTestRule(LoginActivity.class);
#Before
public void setUp() throws Exception {
initVariables();
}
protected void initVariables() {
solo = new Solo(InstrumentationRegistry.getInstrumentation(), activityTestRule.getActivity());
}
#Test
public void testLayout() {
solo.waitForFragmentByTag(LoginFragment.TAG, 1000);
onView(withId(R.id.email_input)).perform(clearText(), typeText("developer#appppp.com"));
onView(withId(R.id.pass_input)).perform(clearText(), typeText("qqqqqqqq"));
onView(withId(R.id.login_button)).perform(click());
solo.waitForDialogToOpen();
}
}
When I want to run my tests I got:
Client not ready yet..
Started running tests
Test running failed: Instrumentation run failed due to 'android.os.NetworkOnMainThreadException'
Empty test suite.
[Edit]
This is MockApiModule which extends ApiModule class
public class MockApiModule extends ApiModule {
private MockWebServer mockWebServer;
public MockApiModule(MockWebServer mockWebServer) {
this.mockWebServer = mockWebServer;
}
#Override
public OkHttpClient provideOkHttpClient(DataManager dataManager) {
return new OkHttpClient.Builder()
.build();
}
#Override
public Retrofit provideRetrofit(OkHttpClient okHttpClient) {
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(mockWebServer.url("/")) // throw NetworkOnMainThreadException
.addConverterFactory(NullOnEmptyConverterFactory.create())
.addConverterFactory(GsonConverterFactory.create(new Gson()))
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.client(okHttpClient)
.build();
return retrofit;
}
#Override
public ApiService provideApiService(Retrofit retrofit) {
return retrofit.create(ApiService.class);
}
#Override
public ApiClient provideApiManager(Application application, ApiService apiService, DataManager dataManager) {
return new MockApiClient(application, apiService, dataManager, mockWebServer);
}
}
API login request looks like that:
apiService.postLoginUser(userLoginModel).enqueue(new Callback<JsonObject>() {
#Override
public void onResponse(Call<JsonObject> call, Response<JsonObject> response) {
if (response.isSuccess()) {
LoginResponse loginResponse = null;
try {
loginResponse = gson.fromJson(MockResponse.getResourceAsString(this, "login.json"), LoginResponse.class);
} catch (IOException e) {
e.printStackTrace();
}
callback.onLoginSuccess(loginResponse);
} else {
callback.onLoginFail(response.errorBody().toString());
}
}
#Override
public void onFailure(Call<JsonObject> call, Throwable t) {
callback.onLoginFail(t.getMessage());
}
});
It works if I change MockMyApplication into MyApp class of application in MockTestRunner
I get stracktrace
android.os.NetworkOnMainThreadException
at android.os.StrictMode$AndroidBlockGuardPolicy.onNetwork(StrictMode.java:1285)
at java.net.InetAddress.lookupHostByName(InetAddress.java:431)
at java.net.InetAddress.getAllByNameImpl(InetAddress.java:252)
at java.net.InetAddress.getByName(InetAddress.java:305)
at okhttp3.mockwebserver.MockWebServer.start(MockWebServer.java:303)
at okhttp3.mockwebserver.MockWebServer.start(MockWebServer.java:293)
at okhttp3.mockwebserver.MockWebServer.maybeStart(MockWebServer.java:143)
at okhttp3.mockwebserver.MockWebServer.getHostName(MockWebServer.java:172)
at okhttp3.mockwebserver.MockWebServer.url(MockWebServer.java:198)
at com.mooduplabs.qrcontacts.modules.MockApiModule.provideRetrofit(MockApiModule.java:38)
at com.mooduplabs.qrcontacts.modules.ApiModule_ProvideRetrofitFactory.get(ApiModule_ProvideRetrofitFactory.java:23)
at com.mooduplabs.qrcontacts.modules.ApiModule_ProvideRetrofitFactory.get(ApiModule_ProvideRetrofitFactory.java:9)
at dagger.internal.ScopedProvider.get(ScopedProvider.java:46)
at com.mooduplabs.qrcontacts.modules.ApiModule_ProvideApiServiceFactory.get(ApiModule_ProvideApiServiceFactory.java:23)
at com.mooduplabs.qrcontacts.modules.ApiModule_ProvideApiServiceFactory.get(ApiModule_ProvideApiServiceFactory.java:9)
at dagger.internal.ScopedProvider.get(ScopedProvider.java:46)
at com.mooduplabs.qrcontacts.modules.ApiModule_ProvideApiManagerFactory.get(ApiModule_ProvideApiManagerFactory.java:31)
at com.mooduplabs.qrcontacts.modules.ApiModule_ProvideApiManagerFactory.get(ApiModule_ProvideApiManagerFactory.java:11)
at dagger.internal.ScopedProvider.get(ScopedProvider.java:46)
at com.mooduplabs.qrcontacts.activities.BaseActivity_MembersInjector.injectMembers(BaseActivity_MembersInjector.java:44)
at com.mooduplabs.qrcontacts.activities.BaseActivity_MembersInjector.injectMembers(BaseActivity_MembersInjector.java:13)
at com.mooduplabs.qrcontacts.components.DaggerQrContactsAppComponent.inject(DaggerQrContactsAppComponent.java:91)
at com.mooduplabs.qrcontacts.activities.BaseActivity.init(BaseActivity.java:74)
at com.mooduplabs.qrcontacts.activities.BaseActivity.onCreate(BaseActivity.java:64)
at android.app.Activity.performCreate(Activity.java:6367)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1110)
at android.support.test.runner.MonitoringInstrumentation.callActivityOnCreate(MonitoringInstrumentation.java:532)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2404)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2511)
at android.app.ActivityThread.access$900(ActivityThread.java:165)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1375)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:150)
at android.app.ActivityThread.main(ActivityThread.java:5621)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:794)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:684)
Ran into similar issue, just avoid starting your activity right away during testing. You can achieve this by adjusting your test rule like the snippet below,
#Rule
// Do not launch the activity right away
public ActivityTestRule<LoginActivity> activityTestRule = new ActivityTestRule(LoginActivity.class, true, false);
and then starting the mock web server by yourself during test setup. It is basically happening because mockWebServer.url("/") tries to start the mock server for you in case it has not already started. You need to do it first if you don't want to do it later on during test execution (NetworkOnMainThreadException case).

SocketTimeOut exception while using Retrofit

I am just trying to do post api call using Retrofit.The server is responding with correct data.I checked with Postman(Chrome). My code is as follows
public class MainActivity extends Activity implements retrofit2.Callback>{
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final OkHttpClient okHttpClient = new OkHttpClient.Builder()
.connectTimeout(6, TimeUnit.MINUTES)
.readTimeout(6, TimeUnit.MINUTES)
.writeTimeout(6, TimeUnit.MINUTES)
.build();
Gson gson = new GsonBuilder()
.setDateFormat("yyyy-MM-dd'T'HH:mm:ssZ")
.create();
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("http://kokanplaces.com/")
.addConverterFactory(GsonConverterFactory.create(gson)).client(okHttpClient)
.build();
// prepare call in Retrofit 2.0
ApiInterface apiService =
ApiClient.getClient().create(ApiInterface.class);
Call<List<CityModel>> call = apiService.getCitiesList();;
//asynchronous call
call.enqueue(this);
}
#Override
public void onResponse(Call<List<CityModel>> call, Response<List<CityModel>> response) {
int code = response.code();
if (code == 200) {
for (CityModel cityModel : response.body()) {
System.out.println(
cityModel.getCityname() + " (" + cityModel.getCityId() + ")");
}
} else {
Toast.makeText(this, "Did not work: " + String.valueOf(code), Toast.LENGTH_LONG).show();
}
}
#Override
public void onFailure(Call<List<CityModel>> call, Throwable t) {
Toast.makeText(this, "failure", Toast.LENGTH_LONG).show();
System.out.println(t.fillInStackTrace());
t.printStackTrace();
}
}
public interface ApiInterface {
#POST("wp-json/getCities")
Call<List<CityModel>> getCitiesList();
}
Every time it is throwing Socket timeout exception.
Any solution will be great help.
I met the problems like you before. I fixed by adding custom OkHttpClient:
Constants.TIMEOUT_CONNECTION = 60;
private OkHttpClient getOkHttpClient() {
final OkHttpClient okHttpClient = new OkHttpClient.Builder()
.readTimeout(0, TimeUnit.NANOSECONDS)
.connectTimeout(Constants.TIMEOUT_CONNECTION, TimeUnit.SECONDS)
.writeTimeout(Constants.TIMEOUT_CONNECTION, TimeUnit.SECONDS)
// .sslSocketFactory(getSSLSocketFactory())
.build();
return okHttpClient;
}
and retrofitAdapter:
retrofitAdapter = new Retrofit.Builder()
.baseUrl(ConstantApi.BASE_URL)
.addConverterFactory(GsonConverterFactory.create(gson))
.client(getOkHttpClient())
.build();
Remember readTimeout is 0, I am using retrofit 2.1.0. Default timeout of retrofit is 10 seconds. I tried to set readTimeout is 60 seconds but no effect.
Topic tags: Socket closed, socket timeout
Explanation: Retrofit maintains connection which is locking socket.
More: https://github.com/square/okhttp/issues/3146
SOLUTION:
configure connectionPool like in below example:
private OkHttpClient getOkHttpClient() {
final OkHttpClient okHttpClient = new OkHttpClient.Builder()
.connectionPool(new ConnectionPool(0,1,TimeUnit.NANOSECONDS))
.build();
return okHttpClient;
}
Please remember to mark answer as correct :)

Retrofit2 Tail Recursion Using RxJava / RxAndroid

I am really trying to get a hang of using Retrofit with RxJava / RxAndroid. I've done this using normal Retrofit2 Callback method in a previous app without the use of Reactive Programming and it worked fine. So, here is it. I need to Tail Recall a function meant to fetch all Local Government from the server. The API uses pagination (I have to construct the URL with ?page=1, perPage=2). I've to do this till I've the whole data. So, below is my Rx code
public static Observable<LgaListResponse> getPages(Context acontext) {
String token = PrefUtils.getToken(acontext);
BehaviorSubject<Integer> pageControl = BehaviorSubject.<Integer>create(1);
Observable<LgaListResponse> ret2 = pageControl.asObservable().concatMap(integer -> {
if (integer > 0) {
Log.e(TAG, "Integer: " + integer);
return ServiceGenerator.createService(ApiService.class, token)
.getLgas(String.valueOf(integer), String.valueOf(21))
.doOnNext(lgaListResponse -> {
if (lgaListResponse.getMeta().getPage() != lgaListResponse.getMeta().getPageCount()) {
pageControl.onNext(initialPage + 1);
} else {
pageControl.onNext(-1);
}
});
} else {
return Observable.<LgaListResponse>empty().doOnCompleted(pageControl::onCompleted);
}
});
return Observable.defer(() -> ret2);
}
And my ServiceGenerator Class
public class ServiceGenerator {
private static final String TAG = "ServiceGen";
private static OkHttpClient.Builder builder = new OkHttpClient.Builder();
private static Retrofit.Builder retrofitBuilder =
new Retrofit.Builder()
.baseUrl(BuildConfig.HOST)
.addCallAdapterFactory(RxJavaCallAdapterFactory.createWithScheduler(Schedulers.io()))
.addConverterFactory(GsonConverterFactory.create(CustomGsonParser.returnCustomParser()));
public static <S> S createService(Class<S> serviceClass, String token) {
builder.addInterceptor(new HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY));
/*builder.addNetworkInterceptor(new StethoInterceptor());*/
builder.connectTimeout(30000, TimeUnit.SECONDS);
builder.readTimeout(30000, TimeUnit.SECONDS);
if (token != null) {
Interceptor interceptor = chain -> {
Request newRequest = chain.request().newBuilder()
.addHeader("x-mobile", "true")
.addHeader("Authorization", "Bearer " + token).build();
return chain.proceed(newRequest);
};
builder.addInterceptor(interceptor);
}
OkHttpClient client = builder.build();
Retrofit retrofit = retrofitBuilder.client(client).build();
Log.e(TAG, retrofit.baseUrl().toString());
return retrofit.create(serviceClass);
}
public static Retrofit retrofit() {
OkHttpClient client = builder.build();
return retrofitBuilder.client(client).build();
}
public static class CustomGsonParser {
public static Gson returnCustomParser(){
return new GsonBuilder()
.setExclusionStrategies(new ExclusionStrategy() {
#Override
public boolean shouldSkipField(FieldAttributes f) {
return f.getDeclaringClass().equals(RealmObject.class);
}
#Override
public boolean shouldSkipClass(Class<?> clazz) {
return false;
}
})
.create();
}
}
}
So, I noticed on the first call, I get a response, but on the second one, the 440Error is thrown. The URL is formed, but the request throws a 400Error. I don't know why it's throwing a 400 everything is working fine if I use POSTMAN to test. And, I tested with my old code too. The Log is too long, so I put it in pastebin LOGS any help thanks. I've written most of this app with RxAndroid / RxJava. Thanks
I suggest you simplify things (and remove recursion). First build up your pages using something like
public static Observable<LgaListResponse> getPages(Context acontext, int initialPage, int perPage) {
String token = PrefUtils.getToken(acontext);
BehaviorSubject<Integer> pagecontrol = BehaviorSubject.<Integer>create(initialPage);
Observable<LgaListResponse> ret2 = pagecontrol.asObservable().concatMap(
new Func1<Integer,Observable<LgaListResponse>>() {
Observable<LgaListResponse> call(Integer pageNumber) {
if (pageNumber > 0) {
return ServiceGenerator.createService(ApiService.class, token)
.getLgas(String.valueOf(aKey), String.valueOf(perPage))
.doOnNext(
new Action1<LgaListResponse>() {
void call(LgaListResponse page) {
if (page.getMeta().getPage() != page.getMeta().getPageCount()) {
pagecontrol.onNext(page.getMeta().getNextPage());
} else {
pagecontrol.onNext(-1);
}
}
}
);
}
else {
return Observable.<LgaListResponse>empty().doOnCompleted(()->pagecontrol.onCompleted());
}
}
}
);
return Observable.defer(
new Func0<Observable<LgaListResponse>() {
Observable<LgaListResponse> call() {
return ret2;
}
}
);
}
then subscribe to the resulting observable. It looks horrible because I've avoided using lambdas but it should work.

How to return different code and error with MockRestAdapter

I use MockRestAdapter to return mock data in my tests, but I'd also like to test errors (401, 503, UnknownHostException, etc)
For SocketTimeoutException, there's an API, but how about different response code?
I've tried MockWebServer but no matter what I enqueue, I always get a 200 with the mock data from the adapter.
update: I want to run my tests like this:
#RunWith(AndroidJUnit4.class)
#LargeTest
public class LoginActivityTest {
#Test public void goodCredentials() {
activity.login("username", "password");
assert(...); // Got back 200 and user object (from mock)
}
#Test public void wrongCredentials() {
activity.login("username", "wrong_password");
something.setResponse(401, "{error: wrong password}");
assert(...);
}
#Test public void someError() {
activity.login("username", "password");
something.setResponse(503, "{error: server error}");
assert(...);
}
}
update 2:
Found something, rather ugly, but does what I need:
MockApi implements ServiceApi {
public static Throwable throwable;
#Override login(Callback<User> callback) {
if (throwable != null) {
sendError(callback)
} else {
callback.success(new User("{name:test}"));
}
}
private void sendError(Callback callback) {
callback.failure(RetrofitError.unexpectedError("", throwable));
}
}
public class LoginActivityTest {
#Test public void someError() {
MockApi.throwable = new InterruptedIOException()
activity.login("username", "password");
// Assert having a time out message
}
#Test public void someError() {
MockApi.throwable = new UnknownHostException()
activity.login("username", "password");
// Assert having a no internet message
}
}
Still working on it, so any feedback will help :)
It's fairly easy to do. You just need to implement Client and pass it when you build your mock RestAdapter.
Creating client with appropriate response:
Client client = new Client() {
#Override public Response execute(Request request) throws IOException {
final String reason = "Some reason.";
final List<Header> headers = new ArrayList<>();
final TypedString body = new TypedString("");//could be json or what ever you want
final int status = 401;
return new Response(request.getUrl(), status, reason, headers, body);
}
};
And passing it to your RestAdapter.Builder:
RestAdapter restAdapter = new RestAdapter.Builder()
.setEndpoint("https://api.com")
.setClient(client)
.setLogLevel(RestAdapter.LogLevel.FULL)
.build();
restAdapter.create(API.class);

Categories

Resources