【问题标题】:How to mock a Crashlytics static method inside a thrown Exception如何在抛出的异常中模拟 Crashlytics 静态方法
【发布时间】:2017-04-29 08:08:00
【问题描述】:

我正在使用 MVP 架构来构建我的应用程序。我的演示者调用 DataManager,它负责从网络或数据库获取数据。当我使用 RxJava 时,我在 Presenter 中订阅观察者并将适当的状态传递给 UI。我的服务层有 Android 上下文,它还会创建一个我自己类型的异常,它也引用了上下文。

if (isNetworkConnected()) {
                    final Call<ServiceResponse<AppVersion>> call = mService.getAppVersion();
                    try {
                        final Response<ServiceResponse<AppVersion>> response = call.execute();
                        if (response.isSuccessful()) {
                            final ServiceResponse<AppVersion> serviceResponse = response.body(); response.body();
                            if (serviceResponse.isSuccess()) {
                                subscriber.onNext(serviceResponse.getData());
                            } else {
                                subscriber.onError(new CustomException(mContext, response.code(), response.message(), serviceResponse.getErrorList()));
                            }
                        } else {
                            subscriber.onError(new CustomException(mContext, response.code(), response.message(), response.errorBody().string()));
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                        subscriber.onError(e);
                    } finally {
                        subscriber.onCompleted();
                    }
                } else {
                    subscriber.onError(new NoInternetException());
                }

我的 CustomException 还会在 Crashlytics 中记录崩溃。当我对这段代码进行单元测试时,我从 Crashlytics 中得到了一个未初始化的异常。所以我需要模拟Crashlytics的静态方法logException。但是我应该如何传递这个模拟对象,因为演示者不接受这个对象?

 public staticErrorType getErrorType(Throwable throwable) {
        //409: Not handled as its a conflict response code and comes in PUT/POST
        if (throwable instanceof IOException) {
            return ErrorType.NO_INTERNET;
        } else if (throwable instanceof CustomException) {
            final int errorCode = ((CustomException) throwable).mErrorCode;
            if (errorCode == 404) {
                return ErrorType.NOT_FOUND;
            } else if (errorCode == 401) {
                return ErrorType.UNAUTORISED;
            } else if (errorCode == 403) {
                return ErrorType.FORBIDDEN;
            } else if (errorCode == 500 || errorCode == 502) {
                return ErrorType.NO_SERVER_TRY_AGAIN;
            } else if (errorCode > 500 && errorCode < 599) {
                return ErrorType.NO_SERVER_TRY_LATER;
            } else if (errorCode == 1000) {
                return ErrorType.NO_COURSE_ENROLLED;
            } else if (errorCode == 1001) {
                return ErrorType.NO_COURSE_STARTED;
            }
        }
        if (throwable != null) {
            Crashlytics.logException(throwable);
        }
        return ErrorType.SOME_THING_WENT_WRONG;
    }

【问题讨论】:

  • 这听起来像是您的课程设计的问题。 CustomException 本身不应记录到 Crashlyrics。相反,订阅者应有责任在必要时进行登录。
  • @DavidRawson 所以自定义异常应该由其他人订阅,或者我应该将自定义记录器类传递给异常,它将异常记录到任何平台,比如 Crashlytics 并模拟它以进行测试?
  • 感谢您的编辑 - 这使问题更加清晰。一个好的解决方案是使用包装类来包装 Crashlytics 中的静态方法。然后将包装类作为订阅者的依赖项传入。见this answer
  • @DavidRawson CustomException的静态方法应该怎么做,它应该是方法本身的参数,因为静态方法不能被删除。当这个 throwable 被发送到 Presenter 时,我需要知道我通过这个方法调用得到的 Presenter 中的错误类型,并且据此我需要告诉视图进行渲染。所以我可以将这个 CustomLogger 类从演示者传递给静态方法,还是应该遵循任何其他设计模式?
  • 我会更新我的答案

标签: android unit-testing mocking crashlytics


【解决方案1】:

另一种方法是使用 powermock 模拟包含静态方法的 Crashlytics 类。

测试向 Crashlytics 发送日志的函数可能如下所示

@RunWith(PowerMockRunner.class)
@PrepareForTest({Crashlytics.class})
@PowerMockIgnore({"javax.net.ssl.*"})
public class presenterTest{

  @Test public void testFunctionWithCrashlyticsCall() throws Exception{
      PowerMockito.mockStatic(Crashlytics.class);
      ...
      assertEquals(..)
   }

}

可在此处找到相关文档: https://github.com/powermock/powermock/wiki/Mockito#mocking-static-method

【讨论】:

    【解决方案2】:

    你遇到的是some argue是静态方法的问题。静态方法logException() 在任何地方添加似乎都无害,但它实际上隐藏了一个真正的依赖关系。由于该类现在需要该依赖项才能运行,因此它使您的类难以测试。

    一个好的解决方案是使用非静态方法创建一个包装类。如果我们将this answer 中的解决方案应用到您的代码中,它看起来像这样:

    public class CrashLyticsWrapper { 
        public CrashLyticsWrapper() {} 
    
        public void logException(Throwable t) {
             CrashLytics.logException(t);
        }
    }
    

    然后可以在构造函数中将其作为依赖项传递给需要它的类。由于它是一个非静态依赖项和一个您现在可以控制的类,因此您可以轻松地模拟它并在必要时对其进行验证。

    一个单独的问题:异常就像值对象,在大多数情况下不应具有静态或非静态依赖项。做这样的事情:

    public CustomException extends RuntimeException() {
         public CustomException() {
              Crashlytics.logException(this); //don't do this!
         }
    }
    

    使代码变得脆弱且无法测试。您可以从从子类化异常中覆盖的构造函数中看到,有一个原因字段,一个消息字段,仅此而已。一个很好的例外范围。如果您需要添加与异常相关的功能,您应该编写单独的错误处理程序,将异常作为数据或参数。这符合SOLID中的“单一职责”原则。

    【讨论】:

      猜你喜欢
      • 2023-03-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-03-09
      • 1970-01-01
      • 2023-03-20
      相关资源
      最近更新 更多