Android Retrofit Mock Response Tutorial

Writing test cases is a difficult task when handling of different server responses. Testing error responses can be quite problematic and your app might not cover all the different scenarios. In order to test your Android apps, one thing that normally gets frequently overlooked is the apps ability to handle different server responses. We can make the client return different responses which are stored in .JSON files within the test project. In this example, I will discuss on implementing Android retrofit mock response by custom Retrofit Client to mock  HTTP response codes. 

Creating a New Project

1. Open Android Studio IDE in your computer.
2. Create a new project and Edit the Application name to “RetrofitMockResponseExample”.
(Optional) You can edit the company domain or select the suitable location for current project tutorial. Then click next button to proceed.
3. Select Minimum SDK (API 15:Android 4.0.3 (IceCreamSandwich). I choose the API 15 because many Android devices currently support more than API 15. Click Next button.
4. Choose “Empty Activity” and Click Next button
5. Lastly, press finish button.

Add new dependency

Go to the build.gradle file and add the retrofit dependency in your project.

compile 'com.squareup.retrofit2:retrofit:2.1.0'
compile 'com.google.code.gson:gson:2.7'
compile 'com.squareup.retrofit2:converter-gson:2.1.0'
compile 'com.squareup.okhttp3:logging-interceptor:3.4.1'

Edit Android.Manifest.xml

Open Android Manifest file and Insert internet permission in order to call API.

<uses-permission android:name="android.permission.INTERNET" />

Add an assets folder

To create a new assets folder, you can refer to the following image.

Add a new .json file

Create folder “mock.api” in the assets folder and create another folder “user” in the “mock.api” folder. After that, create a new JSON file “login” and copy the source code below.

{
  "status": "success",
  "data": {
    "username": "abc",
    "email": "abc@gmail.com"
  },
  "message": ""
}

Create fake interceptor java class

Create a new class “FakeInterceptor” and implements Interceptor on it. The API response will make force to use the .json file from the assets.

import android.content.Context;
import android.util.Log;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;

import okhttp3.Interceptor;
import okhttp3.MediaType;
import okhttp3.Protocol;
import okhttp3.Response;
import okhttp3.ResponseBody;

/**
 *
 */
public class FakeInterceptor implements Interceptor {
    private static final String TAG = FakeInterceptor.class.getSimpleName();
    private static final String FILE_EXTENSION = ".json";
    private Context mContext;

    private String mContentType = "application/json";

    public FakeInterceptor(Context context) {
        mContext = context;
    }

    /**
     * Set content type for header
     * @param contentType Content type
     * @return FakeInterceptor
     */
    public FakeInterceptor setContentType(String contentType) {
        mContentType = contentType;
        return this;
    }

    @Override
    public Response intercept(Chain chain) throws IOException {
        List<String> listSuggestionFileName = new ArrayList<>();
        String method = chain.request().method().toLowerCase();

        Response response = null;
        // Get Request URI.
        final URI uri = chain.request().url().uri();
        Log.d(TAG, "--> Request url: [" + method.toUpperCase() + "]" + uri.toString());

        String defaultFileName = getFileName(chain);

        //create file name with http method
        //eg: getLogin.json
        listSuggestionFileName.add(method + upCaseFirstLetter(defaultFileName));

        //eg: login.json
        listSuggestionFileName.add(defaultFileName);

        String responseFileName = getFirstFileNameExist(listSuggestionFileName, uri);
        if (responseFileName != null) {
            String fileName = getFilePath(uri, responseFileName);
            Log.d(TAG, "Read data from file: " + fileName);
            try {
                InputStream is = mContext.getAssets().open(fileName);
                BufferedReader r = new BufferedReader(new InputStreamReader(is));
                StringBuilder responseStringBuilder = new StringBuilder();
                String line;
                while ((line = r.readLine()) != null) {
                    responseStringBuilder.append(line).append('\n');
                }
                Log.d(TAG, "Response: " + responseStringBuilder.toString());
                response = new Response.Builder()
                        .code(200)
                        .message(responseStringBuilder.toString())
                        .request(chain.request())
                        .protocol(Protocol.HTTP_1_0)
                        .body(ResponseBody.create(MediaType.parse(mContentType), responseStringBuilder.toString().getBytes()))
                        .addHeader("content-type", mContentType)
                        .build();
            } catch (IOException e) {
                Log.e(TAG, e.getMessage(), e);
            }
        } else {
            for (String file : listSuggestionFileName) {
                Log.e(TAG, "File not exist: " + getFilePath(uri, file));
            }
            response = chain.proceed(chain.request());
        }

        Log.d(TAG, "<-- END [" + method.toUpperCase() + "]" + uri.toString());
        return response;
    }

    private String upCaseFirstLetter(String str) {
        return str.substring(0, 1).toUpperCase() + str.substring(1);
    }

    private String getFirstFileNameExist(List<String> inputFileNames, URI uri) throws IOException {
        String mockDataPath = uri.getHost() + uri.getPath();
        mockDataPath = mockDataPath.substring(0, mockDataPath.lastIndexOf('/'));
        Log.d(TAG, "Scan files in: " + mockDataPath);
        //List all files in folder
        String[] files = mContext.getAssets().list(mockDataPath);
        for (String fileName : inputFileNames) {
            if (fileName != null) {
                for (String file : files) {
                    if (fileName.equals(file)) {
                        return fileName;
                    }
                }
            }
        }
        return null;
    }

    private String getFileName(Chain chain) {
        String fileName = chain.request().url().pathSegments().get(chain.request().url().pathSegments().size() - 1);
        return fileName.isEmpty() ? "index" + FILE_EXTENSION : fileName + FILE_EXTENSION;
    }

    private String getFilePath(URI uri, String fileName) {
        String path;
        if (uri.getPath().lastIndexOf('/') != uri.getPath().length() - 1) {
            path = uri.getPath().substring(0, uri.getPath().lastIndexOf('/') + 1);
        } else {
            path = uri.getPath();
        }
        return uri.getHost() + path + fileName;
    }
}

Add a new package folder “models”

Right-click your package name and create a new package folder “models” for a retrofit response.

Create java class for Response

Create class “ResponseStatus” in the “models” package folder, this is getting the status and message of the response.

class ResponseStatus {
    private String status;
    private String message;

    public String getStatus() {
        return status;
    }

    public String getMessage() {
        return message;
    }
}

In the “models” package folder, create another class “ResponseData” is to get the JSON response data from this object. It will extend the “ResponseStatus” class.

import com.google.gson.JsonElement;

public final class ResponseData extends ResponseStatus {
    private JsonElement data;

    public JsonElement getData() {
        return data;
    }
}

Add another new package folder “api”

Right-click your package name and create a new package folder “api” for a retrofit request.

Create a new java class

Right-click the api folder and create a new class “RestClient”. After that insert the following code to this class. This will add the FakeInterceptor in order to use the JSON file from the assets.

import android.content.Context;


import com.questdot.retrofitmockresponseexample.FakeInterceptor;
import okhttp3.OkHttpClient;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;

/**
 *
 */
public final class RestClient {

    private static RestService mRestService = null;

    public static RestService getClient(Context context) {
        if (mRestService == null) {
            final OkHttpClient client = new OkHttpClient
                    .Builder()
                    .addInterceptor(new FakeInterceptor(context))
                    .build();

            final Retrofit retrofit = new Retrofit.Builder()
                    .addConverterFactory(GsonConverterFactory.create())
                    .baseUrl("//mock.api")
                    .client(client)
                    .build();

            mRestService = retrofit.create(RestService.class);
        }
        return mRestService;
    }
}

Create a new interface class

In the API folder, create an interface “RestService” so that you able to call the login API according to the query and path.

import com.questdot.retrofitmockresponseexample.models.ResponseData;

import retrofit2.Call;
import retrofit2.http.GET;
import retrofit2.http.Query;


public interface RestService {

    @GET("/user/login")
    Call<ResponseData> login(@Query("username") final String id,
                             @Query("pwd") final String pwd);

}

 Edit activity_main.xml layout

Go to this activity_main.xml file and it will add an id in your textview.

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="//schemas.android.com/apk/res/android"
    xmlns:app="//schemas.android.com/apk/res-auto"
    xmlns:tools="//schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.questdot.retrofitmockresponseexample.MainActivity">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        android:id="@+id/txtResponse"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</android.support.constraint.ConstraintLayout>

Edit MainActivity.java class

Open MainActivity.java class and this class will be called the login API and display it on the screen.

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;

import com.questdot.retrofitmockresponseexample.api.RestClient;
import com.questdot.retrofitmockresponseexample.models.ResponseData;

import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;

public class MainActivity extends AppCompatActivity {

    TextView txtResponse;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        txtResponse = (TextView)findViewById(R.id.txtResponse);

        RestClient.getClient(this).login("abc", "123456").enqueue(new Callback<ResponseData>() {
            @Override
            public void onResponse(Call<ResponseData> call, Response<ResponseData> response) {
                txtResponse.setText(response.body().getData().toString());
                Log.d("MainActivity", response.body().getData().toString());
            }

            @Override
            public void onFailure(Call<ResponseData> call, Throwable t) {

            }
        });
    }
}

Run Your Project

Lastly, you can now run your android project and view the mock json response from your devices.

(Android Retrofit Mock Response)

Source Code

(Visited 6,447 times, 1 visits today)
Advertisements

Yong Loon Ng

Ng Yong Loon, better known as Kristofer is a software engineer and computer scientist who doubles up as an entrepreneur.

You may also like...

3 Responses

  1. phani says:

    hi brother
    Can i get the source code for this example

  2. Jose says:

    Hi! if i call xxx from different activities will may to produce crashes for context problems?

  3. Jose says:

    Hi! if i call RestClient.getClient(context) from different activities will may to produce crashes for context problems?

Leave a Reply

Your email address will not be published. Required fields are marked *