Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unit testing Retrofit api call with Mockito - ArgumentCaptor

Pardon me if my question looks duplicated but I am not getting how to test retrofit API call. build.gradle at application level

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    androidTestCompile("com.android.support.test.espresso:espresso-core:$rootProject.ext.expressoVersion", {
        exclude group: 'com.android.support', module: 'support-annotations'
    })
    compile "com.android.support:appcompat-v7:$rootProject.ext.supportLibraryVersion"
    compile "com.jakewharton:butterknife:$rootProject.ext.butterKnifeVersion"
    annotationProcessor "com.jakewharton:butterknife-compiler:$rootProject.ext.butterKnifeVersion"

    // Dependencies for local unit tests
    testCompile "junit:junit:$rootProject.ext.junitVersion"
    testCompile "org.mockito:mockito-all:$rootProject.ext.mockitoVersion"
    testCompile "org.hamcrest:hamcrest-all:$rootProject.ext.hamcrestVersion"
    testCompile "org.powermock:powermock-module-junit4:$rootProject.ext.powerMockito"
    testCompile "org.powermock:powermock-api-mockito:$rootProject.ext.powerMockito"
    compile "com.android.support.test.espresso:espresso-idling-resource:$rootProject.ext.espressoVersion"

    // retrofit, gson
    compile "com.google.code.gson:gson:$rootProject.ext.gsonVersion"
    compile "com.squareup.retrofit2:retrofit:$rootProject.ext.retrofitVersion"
    compile "com.squareup.retrofit2:converter-gson:$rootProject.ext.retrofitVersion"
}

build.gradle at project level has this extra content

// Define versions in a single place

ext {
    // Sdk and tools
    minSdkVersion = 15
    targetSdkVersion = 25
    compileSdkVersion = 25
    buildToolsVersion = '25.0.2'

    supportLibraryVersion = '23.4.0'
    junitVersion = '4.12'
    mockitoVersion = '1.10.19'
    powerMockito = '1.6.2'
    hamcrestVersion = '1.3'
    runnerVersion = '0.5'
    rulesVersion = '0.5'
    espressoVersion = '2.2.2'
    gsonVersion = '2.6.2'
    retrofitVersion = '2.0.2'
    butterKnifeVersion = '8.5.1'
    expressoVersion = '2.2.2'
}

MainActivity

public class MainActivity extends AppCompatActivity implements MainView {

    @BindView(R.id.textViewApiData)
    TextView mTextViewApiData;
    @BindView(R.id.progressBarLoading)
    ProgressBar mProgressBarLoading;

    private MainPresenter mMainPresenter;

    @Override
    protected void onCreate(final Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ButterKnife.bind(this);
        initializeComponents();
    }

    private void initializeComponents() {
        mMainPresenter = new MainPresenter(this);
        mMainPresenter.presentDataFromApi();
    }

    @Override
    public void onResponseReceived(final String response) {
        mTextViewApiData.setText(response);
    }

    @Override
    public void onErrorReceived(final String message) {
        mTextViewApiData.setText(message);
    }

    @Override
    public void showProgressDialog(final boolean enableProgressDialog) {
        mProgressBarLoading.setVisibility(enableProgressDialog ? View.VISIBLE : View.GONE);
    }
}

MainView

public interface MainView {

    void onResponseReceived(String response);

    void onErrorReceived(String message);

    void showProgressDialog(boolean enableProgressDialog);
}

ApiClient

public class ApiClient {

    private static Retrofit sRetrofit;
    public static Retrofit getInstance() {
        if (sRetrofit == null) {
            sRetrofit = new Retrofit.Builder()
                    .baseUrl(Constants.Urls.BASE_URL)
                    .addConverterFactory(GsonConverterFactory.create())
                    .build();
        }
        return sRetrofit;
    }
}

Presenter

public class MainPresenter {
    private final MainView mMainView;
    private final Call<List<UserResponse>> mCallListUserResponse;

    public MainPresenter(final MainView mainView) {
        this.mMainView = mainView;
        final ApiInterface apiInterface = ApiClient.getInstance().create(ApiInterface.class);
        mCallListUserResponse = apiInterface.getUsers();
    }

    public void presentDataFromApi() {
        mMainView.showProgressDialog(true);
        mCallListUserResponse.enqueue(new Callback<List<UserResponse>>() {
            @Override
            public void onResponse(final Call<List<UserResponse>> call,
                                   final Response<List<UserResponse>> response) {
                mMainView.onResponseReceived(Constants.DummyData.SUCCESS);
                mMainView.showProgressDialog(false);
            }

            @Override
            public void onFailure(final Call<List<UserResponse>> call, final Throwable t) {
                mMainView.onErrorReceived(Constants.DummyData.ERROR);
                mMainView.showProgressDialog(false);
            }
        });
    }
}

ApiInterface

public interface ApiInterface {
    @GET(Constants.Urls.USERS)
    Call<List<UserResponse>> getUsers();
}

Constants

public class Constants {
    public class Urls {
        public static final String BASE_URL = "https://jsonplaceholder.typicode.com";
        public static final String USERS = "/users";
    }
}

This is what I am trying to do and it's not working. Test case will pass for now as i have commented 3 of the last lines. You can view the error once you un-comment those lines.
TestCase

public class MainPresenterTest {

    @InjectMocks
    private MainPresenter mMainPresenter;
    @Mock
    private MainView mMockMainView;
    @Mock
    private Call<List<UserResponse>> mUserResponseCall;
    @Captor
    private ArgumentCaptor<Callback<List<UserResponse>>> mArgumentCaptorUserResponse;

    @Before
    public void setUp() throws Exception {
        MockitoAnnotations.initMocks(this);
    }

    @Test
    public void presentDataFromApiTest() throws Exception {
        mMainPresenter.presentDataFromApi();
        verify(mMockMainView).showProgressDialog(true);
//        verify(mUserResponseCall).enqueue(mArgumentCaptorUserResponse.capture());
//        verify(mMockMainView).onResponseReceived(Constants.DummyData.SUCCESS);
//        verify(mMockMainView).showProgressDialog(false);
    }
}

Log

Wanted but not invoked:
mUserResponseCall.enqueue(
    <Capturing argument>
);
-> at com.example.ranaranvijaysingh.testingdemo.presenters.MainPresenterTest.presentDataFromApiTest(MainPresenterTest.java:69)
Actually, there were zero interactions with this mock.
like image 684
Rana Ranvijay Singh Avatar asked Oct 18 '22 17:10

Rana Ranvijay Singh


1 Answers

Your code looks correct syntactically. However, I suspect that the @InjectMock is not able to inject the mock object to the final instance variables. It is possible that when you call mMainPresenter.presentDataFromApi(), the variable below is being used as real instance:

private final Call<List<UserResponse>> mCallListUserResponse;

You should try injecting the mock variable manually into this class and assign to mCallListUserResponse to be able to gain from mockito instantiation.

It might worth trying following steps:

  1. Make the variable mCallListUserResponse in MainPresenter as non-final.

  2. Add a method in class MainPresenter as below:

    void setUserResponseCall(Call> userResponse){ mCallListUserResponse = userResponse; }

  3. Now in Test class do the following:

Modify your test as below

@Test
public void presentDataFromApiTest() throws Exception {
        //Set mock instance of the user response
        mMainPresenter.setUserResponseCall(mUserResponseCall);

        //real object call to presentDataFromApi();
        mMainPresenter.presentDataFromApi();

        verify(mMockMainView).showProgressDialog(true);    
      verify(mUserResponseCall).enqueue(mArgumentCaptorUserResponse.capture());
    }

Hope it helps.

like image 175
Shivendra Tiwari Avatar answered Oct 21 '22 01:10

Shivendra Tiwari