1
votes

When I test a simple db written with Room, a test that expects an exception fails when dao functions are declared with suspend. The error is:

java.lang.RuntimeException: Delegate runner 'androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner' for AndroidJUnit4 could not be loaded.

My entity is:

@Entity(indices = [Index(value = ["user_name"], unique = true)])
class User (
    @ColumnInfo(name = "user_name") var name: String,
    @PrimaryKey(autoGenerate = true) var id: Long = 0
)

As you can see, I want to prevent the db to store User with same user_name content.

My dao class is:

@Dao
interface UserDao {
    @Insert
    fun insertUser(user: User): Long

    @Delete
    fun deleteUser(user: User)
}

And my test are:

@Test fun insertUser() {
    val jojo = User("Jojo")
    val id = userDao.insertUser(jojo)

    Assert.assertNotEquals(null, id)
}

// Inserting the same `User` must raise a `SQLiteConstraintException`
@Test(expected = SQLiteConstraintException::class)
fun insertSameUser() {
    val jojo = User("Jojo")

    userDao.insertUser(jojo)
    userDao.insertUser(jojo)
}

At that point all tests succeed as expected.

The problem occurs when I change my dao class to use suspend functions:

@Dao
interface UserDao {
    @Insert
    suspend fun insertUser(user: User): Long

    @Delete
    suspend fun deleteUser(user: User)
}

I change my tests as follow:

@Test fun insertUser()= runBlocking {
    val jojo = User("Jojo")
    val id = userDao.insertUser(jojo)

    Assert.assertNotEquals(null, id)
}

// Inserting the same `User` must raise a `SQLiteConstraintException`
@Test(expected = SQLiteConstraintException::class)
fun insertSameUser() = runBlocking {
    val jojo = User("Jojo")

    userDao.insertUser(jojo)
    userDao.insertUser(jojo)
}

Now testing the suspendable functions fail!

A RuntimeException is raised:

java.lang.RuntimeException: Delegate runner 'androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner' for AndroidJUnit4 could not be loaded.
at androidx.test.ext.junit.runners.AndroidJUnit4.throwInitializationError(AndroidJUnit4.java:92)
at androidx.test.ext.junit.runners.AndroidJUnit4.loadRunner(AndroidJUnit4.java:82)
at androidx.test.ext.junit.runners.AndroidJUnit4.loadRunner(AndroidJUnit4.java:51)
at androidx.test.ext.junit.runners.AndroidJUnit4.<init>(AndroidJUnit4.java:46)
at java.lang.reflect.Constructor.newInstance(Native Method)
at org.junit.internal.builders.AnnotatedBuilder.buildRunner(AnnotatedBuilder.java:104)
at org.junit.internal.builders.AnnotatedBuilder.runnerForClass(AnnotatedBuilder.java:86)
at androidx.test.internal.runner.junit4.AndroidAnnotatedBuilder.runnerForClass(AndroidAnnotatedBuilder.java:63)
at org.junit.runners.model.RunnerBuilder.safeRunnerForClass(RunnerBuilder.java:59)
at org.junit.internal.builders.AllDefaultPossibilitiesBuilder.runnerForClass(AllDefaultPossibilitiesBuilder.java:26)
at androidx.test.internal.runner.AndroidRunnerBuilder.runnerForClass(AndroidRunnerBuilder.java:153)
at org.junit.runners.model.RunnerBuilder.safeRunnerForClass(RunnerBuilder.java:59)
at androidx.test.internal.runner.TestLoader.doCreateRunner(TestLoader.java:73)
at androidx.test.internal.runner.TestLoader.getRunnersFor(TestLoader.java:104)
at androidx.test.internal.runner.TestRequestBuilder.build(TestRequestBuilder.java:789)
at androidx.test.runner.AndroidJUnitRunner.buildRequest(AndroidJUnitRunner.java:543)
at androidx.test.runner.AndroidJUnitRunner.onStart(AndroidJUnitRunner.java:386)
at android.app.Instrumentation$InstrumentationThread.run(Instrumentation.java:1959)
Caused by: java.lang.reflect.InvocationTargetException
at java.lang.reflect.Constructor.newInstance(Native Method)
at androidx.test.ext.junit.runners.AndroidJUnit4.loadRunner(AndroidJUnit4.java:72)
... 16 more
Caused by: org.junit.runners.model.InitializationError
at org.junit.runners.ParentRunner.validate(ParentRunner.java:418)
at org.junit.runners.ParentRunner.<init>(ParentRunner.java:84)
at org.junit.runners.BlockJUnit4ClassRunner.<init>(BlockJUnit4ClassRunner.java:65)
at androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner.<init>(AndroidJUnit4ClassRunner.java:43)
at androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner.<init>(AndroidJUnit4ClassRunner.java:48)
... 18 more

I found that the error comes from the test insertSameUser, and if I change insertSameUser as follow there are no error anymore:

@Test(expected = SQLiteConstraintException::class)
fun insertSameUser() = runBlocking {
    val jojo = User("Jojo")
    userDao.insertUser(jojo)
    userDao.insertUser(jojo)

    Assert.assertEquals(null, "fake")
}

If I remove @RunWith(AndroidJUnit4::class) as suggested here then the test fails with java.lang.Exception: Method insertSameUser() should be void. Here again if I add the dummy test as above then the test works.

Does anyone have an idea ?

The dependencies are:

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
    implementation 'androidx.appcompat:appcompat:1.0.2'
    implementation 'androidx.core:core-ktx:1.0.2'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
    testImplementation 'junit:junit:4.12'

    androidTestImplementation 'androidx.test.ext:junit:1.1.0'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'

    implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.3'

    //Room
    def room_version = "2.2.1"
    implementation "androidx.room:room-runtime:$room_version"
    kapt "androidx.room:room-compiler:$room_version" // For Kotlin use kapt instead of annotationProcessor
    // optional - Kotlin Extensions and Coroutines support for Room
    implementation "androidx.room:room-ktx:$room_version"
    // Test helpers
    testImplementation "androidx.room:room-testing:$room_version"
}
Your test function must not return whatever runBlocking returns, at least for me, that was the culprit.Jim Ovejera