Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Simulate Bad Network with Retrofit 2 (on Actual, i.e. Non-mocked, API)

In Retrofit 1.x I used the following pattern to create api service classes that simulated a bad network connection for certain build variants.

// Retrofit 1
private <T> T create(Class<T> apiServiceClass) {
    T apiService = restAdapter.create(apiServiceClass);
    if (!BuildConfig.IS_PRODUCTION_BUILD) {
        endpoints = mockRestAdapter.create(apiServiceClass, apiService);
    }
    return apiService;
}

The cool thing about this is that my actual API endpoints are used. This way I can see how the app behaves on a flaky connection and I don't need to provide mocked/artificial responses.

Now, in Retrofit 2 the API for MockRestAdapter or rather MockRetrofit changed completely. MockRetrofit#create now returns a BehaviorDelegate. If I try to use the same pattern as before

// Retrofit 2
private <T> T create(Class<T> apiServiceClass) {
    T apiService = retrofit.create(apiServiceClass);
    if (!BuildConfig.IS_PRODUCTION_BUILD) {
        endpoints = mockRetrofit.create(apiServiceClass).returning(???);
    }
    return apiService;
}

I get stuck on the returning(???). returning expects a Call<?> implementation. But I can't seem to figure out how to implement it to make it work like my Retrofit 1 example (maybe it's not intended to).

So my question is: In general, how can I achieve the above Retrofit 1 pattern for simulating a bad network on an actual API with Retrofit 2?

like image 853
Eugen Avatar asked Feb 29 '16 13:02

Eugen


1 Answers

Eventually I figured it out. The idea is to use OkHttp's application interceptors. Here's the solution.

First, create a NetworkBehavior.

final NetworkBehavior behavior = NetworkBehavior.create();
behavior.setDelay(2000, TimeUnit.MILLISECONDS);
behavior.setFailurePercent(50);
behavior.setVariancePercent(50);

Of course you can provide behavior to a UI component to dynamically change these values.

When configuring the OkHttpClient add the following interceptor.

final OkHttpClient.Builder builder = new OkHttpClient.Builder();
if (!BuildConfig.IS_PRODUCTION_BUILD) {
    builder.addInterceptor(new HttpLoggingInterceptor());
    builder.addInterceptor(new Interceptor() {
        @Override public Response intercept(Chain chain) throws IOException {
            try {
                Thread.sleep(behavior.calculateDelay(TimeUnit.MILLISECONDS));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (behavior.calculateIsFailure()) {
                return new Response.Builder()
                        .code(500)
                        .message("MockError")
                        .protocol(Protocol.HTTP_1_1)
                        .request(chain.request())
                        .body(ResponseBody.create(MediaType.parse("text/plain"), "MockError"))
                        .build();
            }
            return chain.proceed(chain.request());
        }
    });
}

Note that you should add a logging interceptor before so that request logs are properly shown. The manually created response object can of course be adapted to your liking. Some values are obligatory (e.g. protocol or request). If you don't specify them you will receive NPEs. The same approach would actually also work for Retrofit 1.

like image 117
Eugen Avatar answered Oct 23 '22 07:10

Eugen