Dagger 2 如何让 Android 上的测试更容易?

How does Dagger 2 make testing easier on Android?

使用 DI 的最大优势之一是它使测试变得容易得多(What is dependency injection? backs it too). Most of DI frameworks I've worked with on other programming languages (MEF on .NET, Typhoon on Obj-C/Swift, LaravelPHP 上的 IoC 容器,以及其他一些) 允许开发人员在每个组件的单个入口点上注册依赖项,从而防止 "creation" 对对象本身的依赖。

在我阅读 Dagger 2 文档后,整个 "no reflection" 业务听起来很棒,但我看不出它如何使测试更容易,因为对象仍然在某种程度上创建它们自己的依赖关系。

例如,在 CoffeMaker 示例中:

public class CoffeeApp {
  public static void main(String[] args) {

    // THIS LINE
    CoffeeShop coffeeShop = DaggerCoffeeShop.create();

    coffeeShop.maker().brew();
  } 
}

即使您没有显式调用 new,您仍然必须创建依赖项。

现在,为了更详细的示例,让我们转到 Android Example。 如果你打开 DemoActivity class,你会注意到 onCreate 的实现是这样的:

@Override 
protected void onCreate(Bundle savedInstanceState) {
   super.onCreate(savedInstanceState);
    // Perform injection so that when this call returns all dependencies will be available for use.
   ((DemoApplication) getApplication()).component().inject(this);
}

您可以清楚地看到没有从 DI 组件到实际代码的解耦。总之,您需要 mock/stub ((DemoApplication) getApplication()).component().inject(this); 测试用例(如果可能的话)。

到目前为止,我知道 Dagger 2 非常受欢迎,所以一定有一些我没有看到的东西。那么 Dagger 2 如何让测试变得更容易 classes 呢?我将如何模拟,比方说我的 Activity 所依赖的网络服务 class?我希望答案尽可能简单,因为我只对测试感兴趣。

”允许开发人员在单个入口点上注册依赖项 每个组件" - Dagger 2 中的类似物是定义依赖项的 Modules 和 Components。优点是您无需直接在组件中定义依赖项,因此将它解耦,以便稍后在编写单元测试时可以将 Dagger 2 component 切换为测试版本。

"it sounds great the whole "没有反映“业务” - "no reflection" 不是关于匕首的 "big deal"。 "big deal" 是编译时的完整依赖图验证。其他 DI 框架没有此功能,如果您未能定义如何满足某些依赖项,您将在运行时延迟收到错误。如果错误位于一些很少使用的代码路径中,您的程序可能看起来是正确的,但在将来的某个时候它会失败。

"Even though you're not explicitly calling new, you still have to create your dependency." - 好吧,你总是需要以某种方式启动依赖注入。其他 DI 可能 "hide"/自动化此 activity 但在最后某处执行图形构建。对于匕首 1 和 2,这是在应用程序启动时完成的。对于 "normal" 个应用程序(如您在示例中所示)在 main() 中,对于 android 个应用程序 - 在 Application class.

"You can clearly see there is no decoupling from the DI component, to the actual code" - 是的,你是 100% 正确的。这是因为您不直接控制 Android 中的活动、片段和服务的生命周期,即 OS 为您创建这些对象而 OS 不知道你正在使用 DI。您需要手动注入您的活动、片段和服务。起初这似乎很尴尬,但在现实生活中唯一的问题是有时您可能会忘记在 onCreate() 中注入 activity 并在运行时获得 NPE。

Dagger 2 并没有让测试变得更容易

...除了鼓励您首先注入依赖项之外,这自然使个人 类 更易于测试。

我最近听说,Dagger 2 团队仍在考虑改进测试支持的潜在方法 - 尽管无论正在进行什么讨论,他们似乎都不是很 public。

那我现在要怎么测试呢?

您正确地指出 类 想要显式使用某个组件对它有依赖性。所以... 注入该依赖项! 您必须注入组件 'by hand',但这应该不会太麻烦。

官方方式

目前,官方推荐的为测试交换依赖关系的方法是创建一个测试组件来扩展您的生产组件,然后在必要时使用自定义模块。像这样:

public class CoffeeApp {
  public static CoffeeShop sCoffeeShop;

  public static void main(String[] args) {
    if (sCoffeeShop == null) {
      sCoffeeShop = DaggerCoffeeShop.create();
    }

    coffeeShop.maker().brew();
  } 
}

// Then, in your test code you inject your test Component.
CoffeeApp.sCoffeeShop = DaggerTestCoffeeShop.create();

这种方法适用于您在 运行ning 测试时总是想替换的东西 - 例如您想要 运行 针对模拟服务器的网络代码,或者 IdlingResource 运行ning Espresso 测试的实现。

非官方方式

不幸的是,官方方式可能涉及大量样板代码 - 作为一次性的很好,但如果您只想为一组特定的测试交换单个依赖项,那将是一个真正的痛苦。

我最喜欢的 hack 是简单地扩展具有您要替换的依赖项的模块,然后重写 @Provides 方法。像这样:

CoffeeApp.sCoffeeShop = DaggerCoffeeShop.builder()
    .networkModule(new NetworkModule() {
        // Do not add any @Provides or @Scope annotations here or you'll get an error from Dagger at compile time.
        @Override
        public RequestFactory provideRequestFactory() {
          return new MockRequestFactory();
        }
    })
    .build();

检查 this gist 以获得完整示例。