Dagger 2中的单元测试中的字段注入

8

Dagger文档所建议的,对于单元测试,我们不必涉及Dagger,并且对于提供的示例,这是有道理的:

class ThingDoer {
  private final ThingGetter getter;
  private final ThingPutter putter;

  @Inject ThingDoer(ThingGetter getter, ThingPutter putter) {
    this.getter = getter;
    this.putter = putter;
  }

  String doTheThing(int howManyTimes) { /* … */ }
}

使用这种类结构进行单元测试非常简单,只需模拟getterputter,将它们作为构造函数的参数传递,告诉mockito与这些对象交互时应返回什么,然后对doTheThing(...)进行断言即可。
我在测试时遇到困难的是当我需要对一个类进行单元测试时,其结构类似于以下内容:
class ThingDoer {
    @Inject
    ThingGetter getter;

    @Inject
    ThingPutter putter;

    @Inject
    ThingMaker maker;

    @Inject
    // other 10 objects

    public ThingDoer() {
        App.getThingComponent().inject(this);
    }

    String doTheThing(int howManyTimes) { /* … */ }
}

如您所见,我不再使用构造函数注入,而是改用字段注入。主要原因是避免构造函数中有太多参数。

当所有依赖都使用字段注入提供时,是否有办法在 ThingDoer 中注入模拟依赖项?


1
我仍在努力找到一个好的方法来解决这个问题。其中一种可能的方法是:http://blog.sqisland.com/2015/04/dagger-2-espresso-2-mockito.html。但我发现它不够灵活,因为它是所有测试的单一配置。顺便说一下,你正在尝试做的事情违反了良好的面向对象设计原则——太多的依赖通常意味着违反单一职责原则。不要试图使用Dagger来“隐藏”这一点。这正是我在这篇文章中讨论的DI框架滥用的例子:http://www.techyourchance.com/dependency-injection-android/。 - Vasiliy
1
感谢您的评论。非常好的DI最佳实践文章!最终,我将实现方式从字段注入改为构造函数注入。不过,我仍然有兴趣找到一种适当的方法来使用字段注入对类进行单元测试。 - Andy Res
如果你愿意,就让我知道。你可能想关注这个问题(https://dev59.com/Lp3ha4cB1Zd3GeqPS1it)- 我问了一个不同的问题,但出于同样的原因,由于除了我在上面评论中发布的内容之外没有任何答案,所以我将进行实验并回复如果我发现了什么。 - Vasiliy
请纠正我,但这些字段不都是包可访问的吗?因此,如果测试在同一个包中(通常是这样),您可以简单地执行“thingDoer.getter = ...”等操作,对吧?唯一的问题是构造函数将尝试注入创建的对象...不确定是否应该在构造函数中执行此操作。 - Fred
嘿@Fred,你说得有道理。我会试一试的。 - Andy Res
据我所知,由于它在测试中会产生问题,因此通常不建议使用字段注入。但不幸的是,在活动中可能需要使用它。一种方法是像@Fred建议的那样,直接用模拟替换注入的字段。但这并不总是有效的,例如,如果您想要替换一个在onCreate中使用的注入字段,则在您可以替换它之前就已经使用了它。在这种情况下,您可以创建一个使用测试组件和测试模块的测试应用程序。测试模块提供了一个模拟,并且您可以在测试组件上添加一个getter以引用该模拟。 - benji
1个回答

1

对于字段注入,您可以创建一个组件和模块,并在单元测试中使用它们。

假设您有单元测试类ThingDoerTest,则可以使组件将依赖项注入到ThingDoerTest而不是ThingDoer,并且模块提供模拟对象而不是真实对象。

在我的项目中,HomeActivity具有字段注入的HomePresenter。以下代码是一些片段。希望这些代码能给您一些想法。

@RunWith(AndroidJUnit4.class)
public class HomeActivityTest implements ActivityLifecycleInjector<HomeActivity>{

    @Rule
    public InjectorActivityTestRule<HomeActivity> activityTestRule = new InjectorActivityTestRule<>(HomeActivity.class, this);

    @Inject
    public HomePresenter mockHomePresenter;

    @Override
    public void beforeOnCreate(HomeActivity homeActivity) {
        Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
        MyApplication myApplication = (MyApplication) instrumentation.getTargetContext().getApplicationContext();

        TestHomeComponent testHomeComponent = DaggerHomeActivityTest_TestHomeComponent.builder()
            .appComponent(myApplication.getAppComponent())
            .mockHomeModule(new MockHomeModule())
            .build();
        testHomeComponent.inject(HomeActivityTest.this);
        homeActivity.setHomeComponent(testHomeComponent);
    }

    @Test
    public void testOnCreate() throws Exception {
        verify(mockHomePresenter).start();
    }

    @ActivityScope
    @Component(
        dependencies = {
            AppComponent.class
        },
        modules = {
            MockHomeModule.class
        }
    )
    public interface TestHomeComponent extends HomeComponent {
        void inject(HomeActivityTest homeActivityTest);
    }

    @Module
    public class MockHomeModule {
        @ActivityScope
        @Provides
        public HomePresenter provideHomePresenter() {
            return mock(HomePresenter.class);
        }
    }

}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接