Search code examples
androidandroid-espressodagger-2

Dagger and Room, not creating DaggerTestAppComoponent.create()


I'm trying to run an end-to-end test on an app that uses Dagger and Room, When I run the app Dagger isn't able to generate DaggerTestAppComponent.create().

I've been following the Dagger codelab to implement the Dagger into the tests.

Here's a link to the repository on Github. https://github.com/Shawn-Nichol/EndToEnd

Step 1. Custom Test Runner User a custom test runner to use a different application class.

class MyCustomTestRunner : AndroidJUnitRunner() {

    override fun newApplication(cl: ClassLoader?, name: String?, context: Context?): Application {
        return super.newApplication(cl, MyTestApplication::class.java.name, context)
    }
}

Step 2 add to app build gradle. The custom test runner is specified in the app build Gradle.

testInstrumentationRunner "com.example.endtoend.dagger.MyCustomTestRunner"

Step 3 load Modules TestAppComponent, Graph for AndroidTest scope set.


@Singleton
@Component(modules = [TestSaveDataStorageModule::class, RoomModule::class, DispatchersModule::class])
interface TestAppComponent : AppComponent {
    @Component.Factory
    interface Factory {
        fun create(@BindsInstance context: Context): AppComponent
    }
}

Step 4 Add dependencies In order to generate DaggerTestAppComponent The kapt needs to act on the androidTest source set.

dependencies {
    ...
    kaptAndroidTest "com.google.dagger:dagger-compiler:$dagger_version"
}

step 5 update application MyApplication

open class MyApplication : Application() {
    val appComponent: AppComponent by lazy {
        initializeComponent()
    }
    open fun initializeComponent(): AppComponent {
        return DaggerAppComponent.factory().create(applicationContext)
    }
}

Step 6 TestApplication

class TestApplication : MyApplication() {

    override fun initializeComponent(): AppComponent {
        return DaggerTestAppComponent.create()
    }
}

After completing these steps, I run a test so Dagger can generate the DaggerTestAppComponent but it doesn't generate a DaggerTestAppComponent.create() creating an unresolved reference in the code.

DaggerTestAppComponent

@SuppressWarnings({
    "unchecked",
    "rawtypes"
})
public final class DaggerTestAppComponent implements TestAppComponent {
  private Provider<MyRepository> myRepositoryProvider;

  private Provider<Context> contextProvider;

  private Provider<MyDatabase> provideDatabaseProvider;

  private Provider<ItemDao> provideDaoProvider;

  private Provider<RoomRepository> roomRepositoryProvider;

  private Provider<CoroutineDispatcher> providesIoDispatcherProvider;

  private Provider<MainViewModel> mainViewModelProvider;

  private DaggerTestAppComponent(DispatchersModule dispatchersModuleParam, Context contextParam) {

    initialize(dispatchersModuleParam, contextParam);
  }

  public static TestAppComponent.Factory factory() {
    return new Factory();
  }

  private MyAdapter myAdapter() {
    return new MyAdapter(mainViewModelProvider.get());
  }

  @SuppressWarnings("unchecked")
  private void initialize(final DispatchersModule dispatchersModuleParam,
      final Context contextParam) {
    this.myRepositoryProvider = MyRepository_Factory.create((Provider) FakeSaveData_Factory.create());
    this.contextProvider = InstanceFactory.create(contextParam);
    this.provideDatabaseProvider = DoubleCheck.provider(RoomModule_Companion_ProvideDatabaseFactory.create(contextProvider));
    this.provideDaoProvider = DoubleCheck.provider(RoomModule_Companion_ProvideDaoFactory.create(provideDatabaseProvider));
    this.roomRepositoryProvider = RoomRepository_Factory.create(provideDaoProvider);
    this.providesIoDispatcherProvider = DispatchersModule_ProvidesIoDispatcherFactory.create(dispatchersModuleParam);
    this.mainViewModelProvider = DoubleCheck.provider(MainViewModel_Factory.create(myRepositoryProvider, roomRepositoryProvider, providesIoDispatcherProvider));
  }

  @Override
  public void inject(MainActivity arg0) {
    injectMainActivity(arg0);
  }

  @Override
  public void inject(ListFragment arg0) {
    injectListFragment(arg0);
  }

  private MainActivity injectMainActivity(MainActivity instance) {
    MainActivity_MembersInjector.injectViewModel(instance, mainViewModelProvider.get());
    return instance;
  }

  private ListFragment injectListFragment(ListFragment instance) {
    ListFragment_MembersInjector.injectRvAdapter(instance, myAdapter());
    return instance;
  }

  private static final class Factory implements TestAppComponent.Factory {
    @Override
    public TestAppComponent create(Context context) {
      Preconditions.checkNotNull(context);
      return new DaggerTestAppComponent(new DispatchersModule(), context);
    }
  }
}

If I don't have a room installed in the app Dagger will generate DaggerTestAppComponent.create(). It's once setup room Dagger stops generating DaggerTestAppComponent.create().

Dagger dependencies

    def dagger_version = "2.33"
    implementation "com.google.dagger:dagger:$dagger_version"
    kapt "com.google.dagger:dagger-compiler:$dagger_version"
    kapt "com.google.dagger:dagger-android-processor:$dagger_version"
    kaptAndroidTest "com.google.dagger:dagger-compiler:$dagger_version"

Build output

> Task :app:compileDebugAndroidTestKotlin FAILED
e: C:\Android\TestingEx\EndToEnd\app\src\androidTest\java\com\example\endtoend\TestApplication.kt: (14, 39): Unresolved reference: create

Solution

  • There appears to be two problems with your Dagger AndroidTest setup.

    The TestApplication is calling a non existent function.

    class TestApplication : MyApplication() {

    override fun initializeComponent(): AppComponent {
        return DaggerTestAppComponent.create()
    } }
    

    You want to return DaggerTestAppComponent.factory().create(), you can see it at the bottom of the generated DaggerTestAppComponent file.

    return DaggerTestAppComponent.factory().create(this)
    

    Number two In the TestAppComponent the create() function returns the dagger AppComponent graph, you'll want to change that so it returns the TestAppComponent graph.

    @Singleton
    @Component(modules = [
        TestSaveDataStorageModule::class,
        RoomModule::class
    ])
    interface TestAppComponent : AppComponent {
    
    
        @Component.Factory
        interface Factory {
            fun create(@BindsInstance context: Context): TestAppComponent
        }
    }