Android TestCase中的Dagger 2依赖注入

unl*_*101 5 java android unit-testing dependency-injection dagger-2

我已经构建了一个示例应用程序(是的,它实际上只是一个示例,并没有多大意义,但有助于理解Dagger 2中的Android干净架构和依赖注入).我的代码可以在github上找到.(过时.看到这篇文章)示例应用程序只是让你输入一个名称EditText,如果按下按钮,你会看到一条消息"Hello YourName"

我有三个不同的组件:ApplicationComponent,ActivityComponentFragmentComponent.FragmentComponent包含三个模块:

  • ActivityModule
  • FragmentModule
  • InteractorModule

InteractorModule提供了一个MainInteractor.

@Module
public class InteractorModule {

    @Provides
    @PerFragment
    MainInteractor provideMainInteractor () {
        return new MainInteractor();
    }
}
Run Code Online (Sandbox Code Playgroud)

在我的Activity-UnitTest中,我想假装这个MainInteractor.这个Interactor只有一个public Person createPerson(String name)可以创建Person对象的方法.在FakeMainInteractor具有相同的方法,但总是创建了一个名为"冒充者" Person对象,你传递的参数了独立的.

public class FakeMainInteractor {
    public Person createPerson(final String name) {
        return new Person("Fake Person");
    }
}
Run Code Online (Sandbox Code Playgroud)

我已经为上面描述的evey组件I创建了TestComponents.在TestFragmentComponent我换InteractorModuleTestInteractorModule.

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

    void inject(MainActivity mainActivity);
}
Run Code Online (Sandbox Code Playgroud)

此示例在非测试上下文中运行良好.在MainActivity我有一个方法调用initializeInjector()我建立的地方FragmentComponent.并onCreate()调用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);
    }
}
Run Code Online (Sandbox Code Playgroud)

这很好用.我的工作MainActivityTest也很好.它测试名称的输入和以下按钮单击的结果.但TextView节目"你好约翰".

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

但正如我告诉我想用的FakeMainInteractor那样会打印出"Hello Fake Person".但我不知道如何在Test中构建依赖图.所以在测试模式下,我希望使用TestComponents和TestModules而不是原始的组件和模块来创建另一个图.那怎么办呢?如何让测试使用FakeMainInteractor

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

对我来说不是一个解决方案,因为它使用的是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"))));
    }

}
Run Code Online (Sandbox Code Playgroud)

MainActivity拥有以下新方法:

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

AndroidApplication 旗下拥有:

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

Dav*_*son 3

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

通过添加以下方法修改当前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;
    }
}
Run Code Online (Sandbox Code Playgroud)

现在在您的测试设置方法中,您可以将真实根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();
}
Run Code Online (Sandbox Code Playgroud)

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