【问题标题】:Android Interactor testingAndroid 交互器测试
【发布时间】:2017-09-16 19:44:57
【问题描述】:

我有以下课程,我正试图弄清楚如何编写单元测试:

@EBean
public class LoginInteractorImpl implements LoginInteractor {

   private final static String LOGIN_INTERACTOR_THREAD_ID = "LOGIN_INTERACTOR_WORKER";

   private UserRepo mUserRepository;
   private Call<ResponseBody> mCall;

   @Override
   @Background(id = LOGIN_INTERACTOR_THREAD_ID)
   public void login(final String username, final String password, final OnLoginFinishedListener listener) {

      mUserRepository = RetrofitHelper.createClient(UserRepo.class);

      mCall = mUserRepository.doLogin(Credentials.basic(username, password));

      mCall.enqueue(new Callback<ResponseBody>() {
          @Override
          public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
              final int code = response.code();
              if (code == 200) {
                  listener.onSuccess(response.headers().get(""));
              } else if (code == 401) {
                  listener.invalidCredentialsFailure();
              } else if (code >= 400 && code < 500) {
                  listener.invalidCredentialsFailure();
              } else if (code >= 500 && code < 600) {
                  listener.noServerResponseFailure();
              } else {
                  APIError error = ErrorUtils.parseError(response);
                  listener.onError("Unexpected response: code:"
                        + error.getStatusCode()
                        + "message: " + error.getMessage());
              }
          }

           @Override
           public void onFailure(Call<ResponseBody> call, Throwable t) {
              if (t instanceof IOException) {
                  listener.noNetworkFailure();
              } else if(call.isCanceled()) {
                  Log.e(TAG, "request was aborted");
              } else {
                  listener.onError(t.getMessage());
              }
           }
       });
   }

   @Override
   public void cancel() {
     mCall.cancel();
     BackgroundExecutor.cancelAll(LOGIN_INTERACTOR_THREAD_ID, true);
     }
}

使用 Retrofit 我正在调用我的 web 服务来获得身份验证,并且 web 服务应该返回一个 Authtoken 以供以后使用。我只是无法掌握如何测试这个。我尝试使用 Mockito 创建解决方案,但我只是不知道如何测试登录方法的逻辑。有没有 Mockito/Retrofit 方面的专家可以指导我更接近可行的解决方案?

【问题讨论】:

    标签: java android unit-testing mockito retrofit2


    【解决方案1】:

    在尝试任何单元测试之前,我会重构很多东西:

    1)enqueue 方法中创建的匿名类创建一个显式回调类。我们称之为 LoginCallback

    public class LoginCallback implements Callback{
        @Override
        public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
                final int code = response.code();
                if (code == 200) {
                ...
        }
    
        @Override
        public void onFailure(Call<ResponseBody> call, Throwable t) {
             if (t instanceof IOException) {
                listener.noNetworkFailure();
               ...
        }
    }
    

    现在您对这两个方法逻辑执行独立的单元测试。

    2) 将静态方法调用移至包级方法。你最终会得到这样的结果:

    public class LoginInteractorImpl implements LoginInteractor {
    
       public void login(final String username, final String password, final OnLoginFinishedListener listener) {
    
         mUserRepository = createClient();
    
         mCall = mUserRepository.doLogin(getCredentials(username,password));
    
         mCall.enqueue(new LoginCallback());
       }
    
        UserRepo createClient(){
           RetrofitHelper.createClient(UserRepo.class)
        }
    
        Credentials getCredentials(String username, String password){
            return Credentials.basic(username, password)
        }
    }
    

    3) 单元测试login 方法

    @RunWith(JunitMockitoRunner.class)
    public class TestClass{
    
        @Spy
        private LoginInteractorImpl loginInterceptor=  new LoginInteractorImpl();
    
        @Mock
        private UserRepo mUserRepositoryMock;
    
        @Mock
        private Call<ResponseBody> mCallMock;
    
        @Mock
        private Credentials credentialsMock;
    
        public void shouldEnqueueWhenLogin(){
            // Arrange
            String username = "name";
            String password = "pass";
            Mockito.doReturn(mUserRepositoryMock).when(loginInterceptor).createClient();
    
           Mockito.doReturn(credentialsMock).when(loginInterceptor).when(getCredentials(username, password));
    
            Mockito.doReturn(mCallMock).when(mUserRepositoryMock).doLogin(credentialsMock);
    
            // Act
            loginInterceptor.login(username, password);
    
            // Assert that proper callback has been passed to enqueue
            verify(mCallMock).enqueue(Mockito.any(LoginCallback.class));
        }
     }
    

    【讨论】:

    • 非常感谢您。使它更清洁,更容易测试。我仍然有一个问题。我正在使用 Android Annotations,它创建了我的交互器类的最终类。这不适用于@Spy。有什么办法可以解决这个问题,还是我应该放弃 Android 注释并自己做线程?
    • 我明白了。那么在这种情况下,我建议使用 PowerMockito。如果您真的无法正确重构代码,它应该是最后的手段,但它会为您解决问题。按照本教程github.com/powermock/powermock/wiki/MockFinal 进行操作,对我的代码进行一些小的更改,您应该能够完成它。如果您遇到一些问题,请告诉我。
    • 我无法让 PowerMockito 与 Robolectric 一起工作,因此我最终得到了一个使用静态初始化程序的糟糕解决方案。您是否看到以下任何问题: private static UserRepo mUserRepository; ... static {mUserRepository = createClient();} 然后我使用 setter 将 mUserRepository 替换为 mock。
    • 忘记我的最后评论。这会产生新的问题。我将不得不进行重大重构并放弃 Android 注释。
    • :) 是的,一旦您开始进行单元测试,您就会意识到在开发 prod 代码时需要考虑一下如何轻松测试它。坚持下去,你会掌握的
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-12-26
    • 1970-01-01
    • 2014-10-25
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多