【问题标题】:Dagger 2 Dependency Injection in Android TestCaseAndroid TestCase 中的 Dagger 2 依赖注入
【发布时间】:2017-03-17 07:03:07
【问题描述】:

我已经构建了一个示例应用程序(是的,这实际上只是一个示例,没有多大意义,但有助于理解 Dagger 2 中的 Android 干净架构和依赖注入)。我的 代码在github 上可用。(已过时。请参阅this 帖子)示例应用程序只是让您在EditText 中输入名称,如果您按下按钮,您会看到一条消息“Hello YourName”

我有三个不同的组件:ApplicationComponentActivityComponentFragmentComponentFragmentComponent 包含三个模块:

  • 活动模块
  • 片段模块
  • 交互模块

InteractorModule 提供MainInteractor

@Module
public class InteractorModule {

    @Provides
    @PerFragment
    MainInteractor provideMainInteractor () {
        return new MainInteractor();
    }
}

在我的 Activity-UnitTest 中,我想伪造这个 MainInteractor。这个交互器只有一个方法public Person createPerson(String name),它可以创建一个Person对象。 FakeMainInteractor 具有相同的方法,但始终创建​​一个名为“Fake Person”的 Person 对象,与您传递的参数无关。

public class FakeMainInteractor {
    public Person createPerson(final String name) {
        return new Person("Fake Person");
    }
}

我已经为上面描述的每一个组件创建了 TestComponents。在TestFragmentComponent 中,我将InteractorModule 替换为TestInteractorModule

@PerFragment
@Component(dependencies = TestApplicationComponent.class, modules = {ActivityModule.class, FragmentModule.class, TestInteractorModule.class})
public interface TestFragmentComponent {
    void inject(MainFragment mainFragment);

    void inject(MainActivity mainActivity);
}

这个例子在非测试环境中运行良好。在MainActivity我有一个名为initializeInjector()的方法,我在其中构建FragmentComponentonCreate()calls onActivitySetup() 其中 致电initializeInjector()inject()

public class MainActivity extends BaseActivity implements MainFragment.OnFragmentInteractionListener,
        HasComponent<FragmentComponent> {


    private FragmentComponent fragmentComponent;
    private Fragment currentFragment;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        if (savedInstanceState == null) {
            currentFragment = new MainFragment();
            addFragment(R.id.fragmentContainer, currentFragment);
        }

    }


    private void initializeInjector() {
        this.fragmentComponent = DaggerFragmentComponent.builder()
                .applicationComponent(getApplicationComponent())
                .activityModule(getActivityModule())
                .fragmentModule(getFragmentModule())
                .build();
    }

    @Override
    protected void onActivitySetup() {
        this.initializeInjector();
        fragmentComponent.inject(this);

    }

    @Override
    public void onFragmentInteraction(final Uri uri) {

    }

    @Override public FragmentComponent getComponent() {
        return fragmentComponent;
    }


    public FragmentModule getFragmentModule() {
        return new FragmentModule(currentFragment);
    }
}

这很好用。我的MainActivityTest也可以正常工作。它测试名称的输入和以下按钮单击的结果。但是TextView 显示“Hello John”。

public class MainActivityTest implements HasComponent<TestFragmentComponent> {

    @Rule
    public ActivityTestRule<MainActivity> mActivityRule = new ActivityTestRule(MainActivity.class, true, true);

    private MainActivity mActivity;
    private TestFragmentComponent mTestFragmentComponent;


    @Before
    public void setUp() throws Exception {
        mActivity = mActivityRule.getActivity();
    }

    @Test
    public void testMainFragmentLoaded() throws Exception {
        mActivity = mActivityRule.getActivity();
        assertTrue(mActivity.getCurrentFragment() instanceof MainFragment);
    }

    @Test
    public void testOnClick() throws Exception {
        onView(withId(R.id.edittext)).perform(typeText("John"));
        onView(withId(R.id.button)).perform(click());
        onView(withId(R.id.textview_greeting)).check(matches(withText(containsString("Hello John"))));

    }


    @Override
    public TestFragmentComponent getComponent() {
        return mTestFragmentComponent;
    }
}

但正如我所说,我想使用 FakeMainInteractor 来打印“Hello Fake Person”。但我不知道如何在测试中建立依赖关系图。所以在测试模式下,我希望创建另一个图表,使用 TestComponents 和 TestModules 而不是原来的 Components 和 Modules。那么该怎么做呢?如何让测试使用FakeMainInteractor

正如我所说,我知道这个示例应用程序没有做任何有用的事情。但我想了解Testing with Dagger 2。我已经阅读了this 文章。但它只是展示了如何制作 TestComponents 和测试模块。它没有说明如何在单元测试中使用测试图。怎么做?有人可以提供一些示例代码吗?

This 对我来说不是一个解决方案,因为它使用 Dagger 2 的旧版本(我使用 2.7 版)并且它确实描述了如何连接 TestComponents。

在尝试@DavidRawson 的方法后,我的一些课程改变了他们的实现:

public class MainActivityTest{

    @Rule
    public ActivityTestRule<MainActivity> mActivityRule = new ActivityTestRule(MainActivity.class, true, true);

    private MainActivity mActivity;
    private TestApplicationComponent mTestApplicationComponent;
    private TestFragmentComponent mTestFragmentComponent;

    private void initializeInjector() {
        mTestApplicationComponent = DaggerTestApplicationComponent.builder()
                .applicationModule(new ApplicationModule(getApp()))
                .build();

        getApp().setApplicationComponent(mTestApplicationComponent);

        mTestFragmentComponent = DaggerTestFragmentComponent.builder()
                .testApplicationComponent(mTestApplicationComponent)
                .activityModule(mActivity.getActivityModule())
                .testInteractorModule(new TestInteractorModule())
                .build();

        mActivity.setFragmentComponent(mTestFragmentComponent);

        mTestApplicationComponent.inject(this);
        mTestFragmentComponent.inject(this);

    }

    public AndroidApplication getApp() {
        return (AndroidApplication) InstrumentationRegistry.getInstrumentation().getTargetContext().getApplicationContext();
    }

    @Before
    public void setUp() throws Exception {
        mActivity = mActivityRule.getActivity();
        initializeInjector();
    }

    @Test
    public void testMainFragmentLoaded() throws Exception {
        mActivity = mActivityRule.getActivity();
        assertTrue(mActivity.getCurrentFragment() instanceof MainFragment);
    }

    @Test
    public void testOnClick() throws Exception {
        onView(withId(R.id.edittext)).perform(typeText("John"));
        onView(withId(R.id.button)).perform(click());
        onView(withId(R.id.textview_greeting)).check(matches(withText(containsString("Hello John"))));
    }

}

MainActivity拥有以下新方法:

@Override
public void setFragmentComponent(final FragmentComponent fragmentComponent) {
    Log.w(TAG, "Only call this method to swap test doubles");
    this.fragmentComponent = fragmentComponent;
}

AndroidApplication 拥有:

public void setApplicationComponent(ApplicationComponent applicationComponent) {
    Log.w(TAG, "Only call this method to swap test doubles");
    this.applicationComponent = applicationComponent;
}

【问题讨论】:

  • Dagger 不用于测试。您首先必须构建您的类以使用 DI,这具有使类更容易使用依赖项的假冒/模拟测试的良好副作用。您可以手动新建依赖项,也可以使用模拟框架。
  • 我的架构已经为 DI 做好了准备。我只是无法将架构应用于测试用例。 “手动新建依赖项”到底是什么意思?
  • 您创建一个FakeMainInteractor 的新实例,并在实例化它时将其注入被测系统。此外,您的赝品和具体实现应该共享一个共同的抽象。
  • @Nkosi 但你的意思是我创建FakeMainInteractorjust 使用它的构造函数。而且“注入”不是指 Dagger 的注入,而只是将新对象应用于系统?

标签: java android unit-testing dependency-injection dagger-2


【解决方案1】:

您可以在Application 中编写一个setter 方法来覆盖根Component

通过添加此方法修改您当前的Application 类:

public class AndroidApplication extends Application {

    @VisibleForTesting
    public void setApplicationComponent(ApplicationComponent applicationComponent) {
        Log.w(TAG, "Only call this method to swap test doubles");
        this.applicationComponent = applicationComponent;
    }
}

现在在您的测试设置方法中,您可以将真正的根 Component 替换为假根:

@Before
public void setUp() throws Exception {
    TestApplicationComponent component = 
      DaggerTestApplicationComponent.builder()
        .applicationModule(new TestApplicationModule()).build();

    getApp().setComponent(component); 

}

private AndroidApplication getApp() {
    return (AndroidApplication) InstrumentationRegistry.getInstrumentation()
      .getTargetContext().getApplicationContext();
}

如果您使用依赖子组件,您可能必须再次在您的BaseActivity 中编写一个名为setComponent 的方法。请注意,添加公共 getter 和 setter 通常是不好的 OO 设计实践,但这是目前使用 Dagger 2 执行密封测试的最简单解决方案。这些方法记录在 here

【讨论】:

  • setApplicationComponent() 真的需要返回ApplicationComponent 还是应该是void
  • @DavidRawson 谢谢,这很有帮助。但不幸的是,测试仍然使用MainInteractor 而不是FakeMainInteractor,尽管我已经实现并调用了这个setComponent 方法。我认为组件被AndroidApplicationMainActivity中的TestComponents覆盖,但仍然注入原始组件。我已将最新的编辑附加到我的帖子中。
  • @DavidRawson 我认为我的问题是我不仅有一个ApplicationComponent,还有一个FragmentComponent。后者并没有真正交换。但是你能看看我的新资源如何解决这个问题吗? github.com/unlimited101/PresenterInjection
  • 我引用的 github 存储库已过时。看到这个帖子:stackoverflow.com/questions/40485911/…
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-07-11
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多