Android TestCase中的Dagger 2dependency injection

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

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

  • ActivityModule
  • FragmentModule
  • InteractorModule

InteractorModule提供了一个MainInteractor

 @Module public class InteractorModule { @Provides @PerFragment MainInteractor provideMainInteractor () { return new MainInteractor(); } } 

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

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

我已经为上面描述的evey Component 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); } 

此示例在非测试上下文中运行良好。 在MainActivity我有一个名为initializeInjector()的方法,我在其中构建FragmentComponent 。 而onCreate()调用onActivitySetup()调用initializeInjector()inject()

 public class MainActivity extends BaseActivity implements MainFragment.OnFragmentInteractionListener, HasComponent { 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 { @Rule public ActivityTestRule 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”。 但我不知道如何在Test中构建依赖图。 因此,在测试模式下,我想要使用TestComponents和TestModules而不是原始的组件和模块创建另一个图形。 那怎么办呢? 如何让测试使用FakeMainInteractor

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

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

在尝试了@DavidRawson后,我的一些类改变了它们的实现:

 public class MainActivityTest{ @Rule public ActivityTestRule 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; } 

您可以在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与假的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执行密封测试的最简单的解决方案。这些方法在此处记录 。