Dagger 2 Injecting Dependencies in Android TestCase

I created an example application (yes, this is really just an example and does not make much sense, but it is good for understanding the pure Android architecture and injecting dependencies in dagger 2). my code is available on github . (Deprecated. See this post.) The sample application simply allows you to enter a name in EditText , and if you click the button, you will see the message "Hello YourName"

I have three different components: ApplicationComponent , ActivityComponent and FragmentComponent . FragmentComponent contains three modules:

  • Activitymodule
  • Fragmentmentmodule
  • InteractorModule

InteractorModule provides MainInteractor .

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

In my Activity-UnitTest I want to fake this MainInteractor . This Interactor has only the public Person createPerson(String name) method, which can create a Person object. FakeMainInteractor has the same method, but always creates a Person object named "Fake Person", regardless of the parameter you pass.

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

I already created TestComponents for the evey component described above. And In TestFragmentComponent I changed InteractorModule to TestInteractorModule .

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

This example works well in a non-test context. In MainActivity , I have a method called initializeInjector() , where I build a FragmentComponent . And onCreate() calls onActivitySetup() , which calls initializeInjector() and 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); } } 

It works great. And my MainActivityTest also works great. It checks the name entry and the result of the next button press. But TextView shows "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; } } 

But, as I said, I want to use a FakeMainInteractor that will print "Hello Fake Person". But I do not know how to create a dependency graph in a test. Therefore, in test mode, I want to create another graph using TestComponents and TestModules instead of the original components and modules. How to do it? How to allow the test to use FakeMainInteractor ?

As I said, I know that this application does nothing useful. But I would like to understand Testing with Dagger 2. I already read this article . But it just shows how to make TestComponents and TestModules. He does not say how to use the test chart in Unit Test. How to do it? Can someone provide some sample code?

This is not for me, because it uses the older version of Dagger 2 (I use version 2.7), and it does not describe how to connect TestComponents.

After trying the @DavidRawson approach, some of my classes changed their implementation:

 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 owns the following new method:

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

AndroidApplication belongs to:

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

You can write the setter method in Application to override the Component root

Modify the current Application class by adding this method:

 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; } } 

Now in your test setup method, you can replace the real Component root with a fake one:

 @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(); } 

If you use dependent subcomponents, you may have to write a method again called setComponent inside your BaseActivity . Note that adding public getters and setters can be generally bad OO design practice, but this is currently the easiest solution to run leakproof tests using dagger 2. These methods are documented.

+2
source

Source: https://habr.com/ru/post/1259237/


All Articles