Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Field injection in unit tests with Dagger 2

As advised in Dagger documentation, for unit testing we don't have to involve Dagger at all, and for the provided example it makes sense:

class ThingDoer {
  private final ThingGetter getter;
  private final ThingPutter putter;

  @Inject ThingDoer(ThingGetter getter, ThingPutter putter) {
    this.getter = getter;
    this.putter = putter;
  }

  String doTheThing(int howManyTimes) { /* … */ }
}

With this class structure it is simple to unit test, just mock the getter and putter, pass them as constructor arguments, instruct mockito what to return when interacting with any of these objects, and then make assertions on doTheThing(...).

Where I am struggling at testing is when I have to unit test a class with a structure similar to this:

class ThingDoer {
    @Inject
    ThingGetter getter;

    @Inject
    ThingPutter putter;

    @Inject
    ThingMaker maker;

    @Inject
    // other 10 objects

    public ThingDoer() {
        App.getThingComponent().inject(this);
    }

    String doTheThing(int howManyTimes) { /* … */ }
}

As you can see, I am no longer using constructor injection, but field injection instead. The main reason is not have too many parameters in the constructor.

Is there a way to inject mock dependencies in ThingDoer when all its dependencies are provided using field injections?

like image 368
Andy Res Avatar asked Jan 05 '17 12:01

Andy Res


People also ask

What is field injection in dagger?

Field Injection:- Field which participates in the injection is called field injection. Dagger doesn't support Injection in private fields and the final field. Because the private field does not bind with our component and the final can't change its value.

What is the difference between Dagger and Dagger2?

x: It is an adaptation of an earlier version created by Square and now maintained by Google. Dagger2 is compile time Dependency injection framework that generates code to connect dependencies at compile time.

What are subcomponents in dagger?

Subcomponent are components that is like an extension to its parent component. Partition the dependencies into different compartments. Avoid the parent component to have too many dependencies bound to it. Subcomponent and its parent component have different scope (of lifespan).

What is unit testing aim?

The main objective of unit testing is to isolate written code to test and determine if it works as intended. Unit testing is an important step in the development process, because if done correctly, it can help detect early flaws in code which may be more difficult to find in later testing stages.


1 Answers

For field injection, you can create a component and a module which are used in unit test.

Suppose you have the unit test class ThingDoerTest, you can make the component injects dependencies to ThingDoerTest instead ThingDoer and the module provides the mock object instead real object.

In my project, HomeActivity has a field injection HomePresenter. Following code are some snippets. Hope the code can give you some idea.

@RunWith(AndroidJUnit4.class)
public class HomeActivityTest implements ActivityLifecycleInjector<HomeActivity>{

    @Rule
    public InjectorActivityTestRule<HomeActivity> activityTestRule = new InjectorActivityTestRule<>(HomeActivity.class, this);

    @Inject
    public HomePresenter mockHomePresenter;

    @Override
    public void beforeOnCreate(HomeActivity homeActivity) {
        Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
        MyApplication myApplication = (MyApplication) instrumentation.getTargetContext().getApplicationContext();

        TestHomeComponent testHomeComponent = DaggerHomeActivityTest_TestHomeComponent.builder()
            .appComponent(myApplication.getAppComponent())
            .mockHomeModule(new MockHomeModule())
            .build();
        testHomeComponent.inject(HomeActivityTest.this);
        homeActivity.setHomeComponent(testHomeComponent);
    }

    @Test
    public void testOnCreate() throws Exception {
        verify(mockHomePresenter).start();
    }

    @ActivityScope
    @Component(
        dependencies = {
            AppComponent.class
        },
        modules = {
            MockHomeModule.class
        }
    )
    public interface TestHomeComponent extends HomeComponent {
        void inject(HomeActivityTest homeActivityTest);
    }

    @Module
    public class MockHomeModule {
        @ActivityScope
        @Provides
        public HomePresenter provideHomePresenter() {
            return mock(HomePresenter.class);
        }
    }

}
like image 153
Sandy Lin Avatar answered Sep 19 '22 06:09

Sandy Lin