Search code examples
androiddependency-injectiondagger-2robolectricandroid-mvp

Using Dagger and Robolectric with test application


I am using MVP pattern with a Fragment(GalleryFragment), where Application class(MainApplication) sources MainActivityRepository and GalleryFragmentPresenter(grouped as DIModules) which are provided to Fragment through field injection.

To test GalleryFragment in isolation, my idea was to use Robolectric configuration(@Config) to replace MainApplication entirely with a custom TestApplication sourcing mockDIModules.

GalleryFragmentTest runs until startFragment(galleryFragment) but I get a NullPointerException at MainApplication.getComponent().inject(this); inside GalleryFragment.

I suspect this is because this line specifically uses MainApplication while everything else is dealt with TestApplication set by Robolectric @Config, but I'm not sure and I am looking for advice on how to successfully run tests using this custom TestApplication.

While searching for possible solutions, I found out about using AndroidInjector from Dagger support library, which will get rid of MainApplication.getComponent().inject(this); entirely but would this work? https://android.jlelse.eu/android-and-dagger-2-10-androidinjector-5e9c523679a3

GalleryFragment.java

public class GalleryFragment extends Fragment {
    @Inject
    public MainActivityRepository mRepository;
    @Inject
    public GalleryFragmentPresenter mGalleryFragmentPresenter;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        MainApplication.getComponent().inject(this);   //NullPointerException here
        mGalleryFragmentPresenter.initialize(mRepository, value);
    }
}

DIModules.java

@Module
public class DIModules {
    @Provides
    public GalleryFragmentPresenter provideGalleryFragmentPresenter(){
        return new GalleryFragmentPresenter();
    }
    @Provides
    @Singleton
    public MainActivityRepository provideMainActivityRepository(){
        return new MainActivityRepository();
    }
}

AppComponent.java

@Singleton
@Component(modules = DIModules.class)
public interface AppComponent {
        void inject(GalleryFragment galleryFragment);
}

MainApplication.java

public class MainApplication extends Application {
    public static AppComponent component;

    @Override
    public void onCreate() {
        super.onCreate();
        Realm.init(this);
        component = buildComponent();
    }
    public static AppComponent getComponent() {
        return component;
    }
    protected AppComponent buildComponent(){
        return DaggerAppComponent
                .builder()
                .dIModules(new DIModules())
                .build();
    }
}

TestApplication.java

public class TestApplication extends Application {
    public static AppComponent component;

    @Override
    public void onCreate() {
        super.onCreate();
        component = buildComponent();
    }
    public static AppComponent getComponent() {
        return component;
    }
    protected AppComponent buildComponent(){
        return DaggerAppComponent.builder()
                .dIModules(new mockDIModules())
                .build();
    }
}

GalleryFragmentTest.java

@RunWith(RobolectricTestRunner.class)
@Config(constants = BuildConfig.class,
        application = TestApplication.class)
public class GalleryFragmentTest {
    @Test
    public void allItemTabTest() throws Exception {
        GalleryFragment galleryFragment = GalleryFragment.newInstance(value);
        startFragment(galleryFragment);
        assertNotNull(galleryFragment);
    }
}

I am using dagger, dagger-android-support, dagger-compiler version 2.14.1 and robolectric:3.6.1


Solution

  • Of course, it is null. Your fragment still tries to work with production application while you're doing things in the test application.

    Change your injection code to next:

    ((MainApplication) getContext().getApplicationContext()).getComponent().inject(this);
    

    And also make the method in your Application getComponent() as not static, so test app overrides it.

    Another option is to change your TestApplication to next:

    public class TestApplication extends Application {
        @Override
        public void onCreate() {
            super.onCreate();
            buildComponent();
        }
    
        private void buildComponent(){
            Application.component = DaggerAppComponent.builder()
                    .dIModules(new mockDIModules())
                    .build();
        }
    }