2

I'm trying to use Dagger 2 for DI in my Android instrumentation tests. It works perfectly fine for Classes/Activities/Fragments in the main application component, but my test component seems to be missing some bindings I can't find. Any ideas on how to go about it would be appreciated. The code I have looks like this:

AssetRepositoryTest

public class AssetRepositoryTest {

    @Nested
    @DisplayName("Given a populated database")
    public class PopulatedDatabaseInstance {

        @Inject
        private TestDatabase database;

        @Inject
        private AssetRepository repository;

        @BeforeEach
        public void setup() {
            final TestApplication application = ApplicationProvider.getApplicationContext();
            application.androidInjector().inject(this);
            
            // Setup database
        }
        
        // Tests
    }
}

Running the instrumentation tests I get the following exception:

java.lang.IllegalArgumentException: No injector factory bound for Class<AssetRepositoryTest.PopulatedDatabaseInstance>

And the dagger related code is as follows:

TestComponent

@Singleton
@Component(modules = {
        AndroidSupportInjectionModule.class,
        TestPersistenceModule.class
})
public interface TestComponent extends AndroidInjector<TestApplication> {

    @Component.Builder
    abstract class Builder extends AndroidInjector.Builder<TestApplication> {
    }
}

TestPersistenceModule

@Module(includes = TestRoomModule.class)
public abstract class TestPersistenceModule {

    @Binds
    abstract AssetRepository bindAssetRepository(final AssetRepositoryImpl repository);
}

TestRoomModule

@Module
public class TestRoomModule {

    @Provides
    @Singleton
    TestDatabase provideTestDatabase(final Application application) {
        return Room.inMemoryDatabaseBuilder(application, TestDatabase.class).build();
    }

    @Provides
    @Singleton
    AssetDao provideAssetDao(final TestDatabase testDatabase) {
        return testDatabase.getAssetDao();
    }
}

TestApplication

public class TestApplication extends DaggerApplication {

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

Besides that, I've a custom AndroidJUnitRunner extension class that overrides the newApplication method and return a TestApplication instance for the test cases.

My AssetRepositoryImpl is as follows:

AssetRepositoryImpl

@Singleton
public class AssetRepositoryImpl extends AbstractRepository<Asset, AssetEntity> implements AssetRepository {

    @Inject
    protected WorkspaceDao workspaceDao;

    @Inject
    public AssetRepositoryImpl(final AssetDao dao, final AssetMapper mapper) {
        super(dao, mapper);
    }
}

The classes that I haven't pasted here have @Inject annotations in their constructors and this code is working properly in the main application with the respective main modules and components.

As a final thought, the AssetRepositoryTest.PopulatedDatabaseInstace, which is instantiated by JUnit, is therefore not instantiated by Dagger and, to my knowledge, that seems to be the problem here.

How can I tell Dagger how to inject these fields into my JUnit test class?

1 Answers1

5

Seems like you are missing some steps in your dagger setup, I've included a checklist that I use.

  • Create CustomTestRunner
class MyCustomTestRunner : AndroidJUnitRunner() {

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

  • Add to Test Runner to app/build.gradle
android {
    ...
    defaultConfig {
        ...
        testInstrumentationRunner "com.example.android.dagger.MyCustomTestRunner"
    }
    ...
}
  • kapt needs to act on AndroidTest source set.
...
dependencies {
    ...
    kaptAndroidTest "com.google.dagger:dagger-compiler:$dagger_version"
}

  • Create Test Modules
  • Create TestAppComponent.
  • Main Application should look something like this
open class MyApplication : Application() {

    val appComponent: AppComponent by lazy {
        initializeComponent()
    }

    open fun initializeComponent(): AppComponent {
        return DaggerAppComponent.factory().create(applicationContext)
    }
}
  • TestApplication
class MyTestApplication : MyApplication() {

    override fun initializeComponent(): AppComponent {
        // Creates a new TestAppComponent that injects fakes types
        return DaggerTestAppComponent.create()
    }
}

If you are looking for more details in what each steps does you can find that information here.

Shawn
  • 1,222
  • 1
  • 18
  • 41