2

I'm trying to test Room Database. For this I need an in memory instance of database. I'm using hilt for DI.

So, i have an app Module in app package:

    @Module
    @InstallIn(SingletonComponent::class)
    object AppModule {
        @Singleton
        @Provides
        fun provideDatabase(
            @ApplicationContext context: Context,
        ) = Room.databaseBuilder(
            context.applicationContext,
            UserDatabase::class.java,
            "user"
        ).build()
    
        @Singleton
        @Provides
        fun provideDao(db: UserDatabase) = db.getUserDao()
}

I have created TestRunner for Hilt and also added it in gradle.

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

Here is gradle

defaultConfig {
    applicationId "package"
    minSdk 24
    targetSdk 31
    versionCode 1
    versionName "1.0"

    testInstrumentationRunner = "package.HiltTestRunner"
}

Here is my TestAppModule

@Module
@TestInstallIn(
    components = [SingletonComponent::class],
    replaces = [AppModule::class]
)
object TestAppModule {

    @Provides
    fun provideInMemoryDb(@ApplicationContext context: Context) =
        Room.inMemoryDatabaseBuilder(
            context,
            UserDatabase::class.java
        ).allowMainThreadQueries().build()
}

And my test class

@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@HiltAndroidTest
class UserDaoTest {

    @get:Rule
    var instantExecutorRule = InstantTaskExecutorRule()

    @get:Rule
    var hiltRule = HiltAndroidRule(this)

    @Inject
    lateinit var database: UserDatabase
    private lateinit var dao: UserDao

    @Before
    fun setup() {
        hiltRule.inject()

        dao = database.getUserDao()
    }

    @Test
    fun insertUser() {
        runTest {
            val user = User(0, "login", "", "", "", 1)
            dao.saveUser(user)

            val dbUser = dao.getUser()
            assertThat(dbUser, equalTo(user))
        }
    }
}

So, when I run the test I'm getting the error that UserDao cannot be provided without @Provides, but i'm not even injecting it in my test class. Can anyone please clarify this?

I noticed that it works just fine if change my TestAppModule like that:

@Module
@InstallIn(SingletonComponent::class)
object TestAppModule {

    @Provides
    @Named("test_db")
    fun provideInMemoryDb(@ApplicationContext context: Context) =
        Room.inMemoryDatabaseBuilder(
            context,
            UserDatabase::class.java
        ).allowMainThreadQueries().build()
}

Basically, here I'm not using TestInstallIn and added a named annotation to function.

pro_go_is
  • 53
  • 1
  • 5
  • It'll also work if you add a dummy / fake method for providing the dao in `TestInstallIn`. When you're using `TestInstallIn`, you're removing all dependencies that replaced module provides or binds. It works with `InstallIn` because you don't nuke the dependencies provided by `AppModule`. – Mel Apr 22 '22 at 15:55

1 Answers1

2

It is because Hilt doesn’t know which database it should inject.

In your "AppModule" and "TestAppModule" you provide the database from the same class (UserDatabase ). So as you mentioned you should use @Named annotation to tell Hilt which database you want to be injected.

Sahar Asadian
  • 121
  • 1
  • 5