Search code examples
androiddagger-2android-instrumentationdagger

Dagger2 Injection Unit Tests is null


Hi i have used dagger for dependency injections of Network Module, ApplicationModule, DatabaseModule, Presenters and interactor in my app. I want to use these same classes and Module during unit testing.

As unit testing reference, i have created AndroidTestAppComponent using following code:

@Singleton
@Component(modules = {
        AndroidSupportInjectionModule.class,
        AndroidTestAppModule.class,
        NetworkModule.class
})
public interface AndroidTestAppComponent extends AndroidInjector<AndroidTestApplication> {
    @Component.Builder
    abstract class AndroidTestAppComponentBuilder extends Builder<AndroidTestApplication> {
    }
}

Giving all module is out of scope for this question, consider AndroidTestAppModule.java below:

public class AndroidTestAppModule {
    @Provides
    @Singleton
    Context provideContext(AndroidTestApplication application) {
        return application.getApplicationContext();
    }

    @Singleton
    @Provides
    KeyguardManager provideKeyguardManager(Context context) {
        return (KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE);
    }

    @Singleton
    @Provides
    FingerprintManagerCompat providerFingerPrintManager(Context context) {
        return FingerprintManagerCompat.from(context);
    }
}

I am able to generate DaggerAndroidTestAppComponent. My Application class is as below:

public class AndroidTestApplication extends DaggerApplication implements HasActivityInjector {
    @Inject
    DispatchingAndroidInjector<Activity> dispatchingActivityInjector;

    AndroidInjector<AndroidTestApplication> androidInjector;

    @Override
    public void onCreate() {
        super.onCreate();
        androidInjector.inject(this);
    }

    @Override
    protected AndroidInjector<? extends DaggerApplication> applicationInjector() {
        androidInjector = DaggerAndroidTestAppComponent.builder().create(this);
        return androidInjector;
    }

    @Override
    public DispatchingAndroidInjector<Activity> activityInjector() {
        return dispatchingActivityInjector;
    }
}

Some other AppPref.java class

@Singleton
public class AppPref {

    private SharedPreferences preferences;

    @Inject
    AppPref(Context context) {
        preferences = context.getSharedPreferences("somefile", Activity.MODE_PRIVATE);
    }
}

As read from documentation: AndroidInjection#inject(T t) t here takes core android module, so when i call this in my Activity AndroidInjection.inject(activity_reference_usually__this__) it works(Normal scenario, real build and no testing app)

Without changing much code how can i use these Classes in AndroidInstrumentationTest, because i will only change test implementation in Test**DaggerModules inside test package.

Sample code for instrumentation is given below:

@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {


    AndroidTestApplication application;

    @Inject
    AppPref appPref;


    @Before
    public void setUp() throws IllegalAccessException, InstantiationException, ClassNotFoundException {
        Context appContext = InstrumentationRegistry.getTargetContext();
        application = (AndroidTestApplication) Instrumentation.newApplication(AndroidTestApplication.class, appContext);
        DaggerAndroidTestAppComponent.builder().create(application).inject(application);
    }

    @Test
    public void useAppContext() {
        // Context of the app under test.
        Context appContext = InstrumentationRegistry.getTargetContext();

        assertEquals("com.a.b", appContext.getPackageName());
    }

    @Test
    public void testPreNotNUll() {
        Assert.assertNotNull(appPref);
    }

}

Ideally, apppref is alwyas null, becuase in setUp method i have injected AndroidTestApplication class and not in ExampleInstrumentedTest how can i edit my dagger2 code so that @Inject works fine and i get valid appPref object. Thank you.


Solution

  • I had to modify @Component interface to skip extending builder from AndroidInjector.Builder and provide my own approach.

    @Singleton
    @Component(modules = {
            AndroidSupportInjectionModule.class,
            AndroidTestAppModule.class,
            NetworkModule.class
    })
    public interface AndroidTestAppComponent extends AndroidInjector<AndroidTestApplication> {
        void inject(ExampleInstrumentedTest test);
    
        @Component.Builder
        abstract class AndroidTestAppComponentBuilder {
            @BindsInstance
            public abstract AndroidTestAppComponentBuilder application(AndroidTestApplication application);
    
            public abstract AndroidTestAppComponent build();
        }
    }
    

    Such that i had to manually pass application and build the component, then as suggested by tuby, i had to add new method void inject(ExampleInstrumentedTest test) to @Component interface.

    My test class now looks like this and i am able to run test and get coverage[jacoco tool]:

    @RunWith(AndroidJUnit4.class)
    public class ExampleInstrumentedTest {
    
    
        @Inject
        AppPref appPref;
    
    
        @Before
        public void setUp() throws IllegalAccessException, InstantiationException, ClassNotFoundException {
            Context appContext = InstrumentationRegistry.getTargetContext();
            AndroidTestApplication application = (AndroidTestApplication) Instrumentation
                    .newApplication(AndroidTestApplication.class, appContext);
            DaggerAndroidTestAppComponent.builder().application(application)
                    .build()
                    .inject(this);
    
        }
    
        @Test
        public void test1AppPrefNotNUll() {
            Assert.assertNotNull(appPref);
        }
    
    
        private final String KEY = "key";
        private final String valid = "test_app";
        private final String invalid = "non_app";
    
    
        @Test
        public void test2AppPrefWrite() {
            appPref.writePreference(KEY, valid);
            Assert.assertNotNull(appPref.readPreference(KEY));
        }
    
        @Test
        public void test3AppPrefRead() {
            Assert.assertEquals(valid, appPref.readPreference(KEY));
        }
    
        @Test
        public void test4AppPrefInvalid() {
            Assert.assertNotNull(invalid, appPref.readPreference(KEY));
        }
    
        @Test
        public void test5AppPrefClear() {
            appPref.clearPreferences();
            Assert.assertEquals(0, appPref.size());
        }
    
    }