【问题标题】:Unit testing Retrofit api call with Mockito - ArgumentCaptor使用 Mockito 进行单元测试 Retrofit api 调用 - ArgumentCaptor
【发布时间】:2017-02-17 13:20:01
【问题描述】:

如果我的问题看起来重复但我不知道如何测试改造 API 调用,请原谅我。 build.gradle 在应用程序级别

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 在项目级别有这个额外的内容

// 在一个地方定义版本

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);
    }
}

主视图

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;
    }
}

演示者

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);
            }
        });
    }
}

API接口

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

常量

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

这是我正在尝试做的,但它不起作用。测试用例现在将通过,因为我已经评论了最后几行中的 3 行。取消注释这些行后,您可以查看错误。
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);
    }
}

日志

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.

【问题讨论】:

  • 请添加完整的测试类..你是如何注入模拟的?使用跑步者还是通过 MockitoAnnotations.initMocks?
  • 我在 setUp 方法中使用了 MockitoAnnotations.initMocks(this)。
  • 我认为你的问题是你需要在mMainPresenter中添加@InjectMocks,并且userResponseCall应该是你的presenter的一个字段,而不是一个局部变量。
  • 我用 mMainPresenter 尝试了@InjectMocks,并将 userResponseCall 作为演示者中的字段。仍然出现同样的错误。
  • 我不确定,但如果 onResponse 和 onFailure 的参数相同,那么您可以在相同的情况下尝试,或者您需要为失败情况编写另一个捕获器。

标签: android mockito retrofit2


【解决方案1】:

您的代码在语法上看起来是正确的。但是,我怀疑 @InjectMock 无法将模拟对象注入最终实例变量。当您调用 mMainPresenter.presentDataFromApi() 时,可能会将以下变量用作真实实例:

private final Call<List<UserResponse>> mCallListUserResponse;

您应该尝试手动将模拟变量注入此类并分配给 mCallListUserResponse 以便能够从模拟实例化中获益。

可能值得尝试以下步骤:

  1. 将 MainPresenter 中的变量 mCallListUserResponse 设为非最终变量。

  2. 在 MainPresenter 类中添加一个方法,如下所示:

    void setUserResponseCall(Call> userResponse){ mCallListUserResponse = 用户响应; }

  3. 现在在 Test 类中执行以下操作:

如下修改你的测试

@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());
    }

希望对你有帮助。

【讨论】:

  • @Shivendra Tiwari 但他没有测试过测试用例对吗?
【解决方案2】:

您需要遵循一些步骤:

  1. 通过运行器或代码注入您的模拟:

    @RunWith(MockitoJUnitRunner.class)
    

    MockitoAnnotations.initMocks(this)
    
  2. @Mock注释你要模拟的字段

  3. @InjectMocks 注释您要测试的字段(在您的情况下是演示者)

  4. 确保您要模拟的字段是您要测试的类中的字段(不是局部变量,例如您的示例中的userResponseCall),否则这些字段将不会被注入。

查看类似示例:https://github.com/matoelorriaga/pokemon/blob/master/app/src/test/java/com/melorriaga/pokemon/presenter/MainPresenterTest.java

【讨论】:

  • 感谢您的回复,我试过了,但仍然遇到同样的错误。
猜你喜欢
  • 1970-01-01
  • 2023-03-10
  • 2015-11-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-02-22
  • 1970-01-01
相关资源
最近更新 更多