Android TestCase中的Dagger 2dependency injection
我已经构建了一个示例应用程序(是的,它实际上只是一个示例,没有多大意义,但有助于理解Dagger 2中的Android清洁架构和dependency injection)。 我的代码可以在github上找到 。(过时。看到这篇文章)示例应用程序只是让你在EditText
输入一个名字,如果按下按钮,你会看到一条消息“Hello YourName”
我有三个不同的组件: ApplicationComponent
, ActivityComponent
和FragmentComponent
。 FragmentComponent
包含三个模块:
- 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
我将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()
的方法,我在其中构建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执行密封测试的最简单的解决方案。这些方法在此处记录 。