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?
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With