Android instrumented test freezes when it tests a suspend function that uses RoomDatabase.withTransaction
Asked Answered
M

2

6

I'm trying to test the following LocalDataSource function, NameLocalData.methodThatFreezes function, but it freezes. How can I solve this? Or How can I test it in another way?

Class to be tested

class NameLocalData(private val roomDatabase: RoomDatabase) : NameLocalDataSource {

  override suspend fun methodThatFreezes(someParameter: Something): Something {
    roomDatabase.withTransaction {
      try {
        // calling room DAO methods here
      } catch(e: SQLiteConstraintException) {
        // ...
      }
      return something
    }
  }
}

Test class

@MediumTest
@RunWith(AndroidJUnit4::class)
class NameLocalDataTest {
  private lateinit var nameLocalData: NameLocalData

  // creates a Room database in memory
  @get:Rule
  var roomDatabaseRule = RoomDatabaseRule()

  @get:Rule
  var instantTaskExecutorRule = InstantTaskExecutorRule()

  @Before
  fun setup() = runBlockingTest {
     initializesSomeData()
     nameLocalData = NameLocalData(roomDatabaseRule.db)
  }

 @Test
 fun methodThatFreezes() = runBlockingTest {
    nameLocalData.methodThatFreezes // test freezes
 }

 // ... others NameLocalDataTest tests where those functions been tested does not use
 // roomDatabase.withTransaction { } 
}

Gradle's files configuration

espresso_version = '3.2.0'
kotlin_coroutines_version = '1.3.3'
room_version = '2.2.5'

test_arch_core_testing = '2.1.0'
test_ext_junit_version = '1.1.1'
test_roboletric = '4.3.1'
test_runner_version = '1.2.0'

androidTestImplementation "androidx.arch.core:core-testing:$test_arch_core_testing"
androidTestImplementation "androidx.test.espresso:espresso-core:$espresso_version"
androidTestImplementation "androidx.test.ext:junit:$test_ext_junit_version"
androidTestImplementation "androidx.test:rules:$test_runner_version"
androidTestImplementation "androidx.test:runner:$test_runner_version"
androidTestImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$kotlin_coroutines_version"
Maggard answered 7/5, 2020 at 2:41 Comment(0)
D
7

Last time I wrote a test for Room database I just simply use runBlock and it worked for me... Could you take a look into this sample and check if it works for you as well?

Edit: Ops! I missed this part... I tried this (in the same sample):

  1. I defined a dummy function in my DAO using @Transaction
@Transaction
suspend fun quickInsert(book: Book) {
    save(book)
    delete(book)
}
  1. I think this is the key of the problem. Add setTransactionExecutor to your Database instantiation.
appDatabase = Room.inMemoryDatabaseBuilder(
    InstrumentationRegistry.getInstrumentation().context,
    AppDatabase::class.java
).setTransactionExecutor(Executors.newSingleThreadExecutor())
    .build()
  1. Finally, the test worked using runBlocking
@Test
fun dummyTest() = runBlocking {
    val dao = appDatabase.bookDao();
    val id = dummyBook.id

    dao.quickInsert(dummyBook)

    val book = dao.bookById(id).first()
    assertNull(book)
}

See this question.

Dalesman answered 7/5, 2020 at 16:51 Comment(1)
Thanks Glauber! In this case this approach didn't work. I take a look on RoomRepository, there the DAO methods are called outside a RoomDatabase.withTransaction { } block. I can make test for the other functions of the DataSource that don't use RoomDatabase.withTransaction { } block with no problems, either using runBlocking or runBlockingTest. The test freezes when I try to test a function that needs the use of RoomDatabase.withTransaction { }.Maggard
M
2

I had tried many things to make this work, used runBlockingTest, used TestCoroutineScope, tried runBlocking, used allowMainThreadQueries, setTransactionExecutor, and setQueryExecutor on my in memory database.

But nothing worked until I found this comment thread in the Threading models in Coroutines and Android SQLite API article in the Android Developers Medium blog, other people mentioned running into this. Author Daniel Santiago said:

I’m not sure what Robolectric might be doing under the hood that could cause withTransaction to never return. We usually don’t have Robolectric tests but we have plenty of Android Test examples if you want to try that route: https://android.googlesource.com/platform/frameworks/support/+/androidx-master-dev/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/SuspendingQueryTest.kt

I was able to fix my test by changing it from a Robolectric test to an AndroidTest and by using runBlocking

This is an example from the google source:

    @Before
    @Throws(Exception::class)
    fun setUp() {
        database = Room.inMemoryDatabaseBuilder(
            ApplicationProvider.getApplicationContext(),
            TestDatabase::class.java
        )
            .build()
        booksDao = database.booksDao()
    }

    @Test
    fun runSuspendingTransaction() {
        runBlocking {
            database.withTransaction {
                booksDao.insertPublisherSuspend(
                    TestUtil.PUBLISHER.publisherId,
                    TestUtil.PUBLISHER.name
                )
                booksDao.insertBookSuspend(TestUtil.BOOK_1.copy(salesCnt = 0))
                booksDao.insertBookSuspend(TestUtil.BOOK_2)
                booksDao.deleteUnsoldBooks()
            }
            assertThat(booksDao.getBooksSuspend())
                .isEqualTo(listOf(TestUtil.BOOK_2))
        }
    }
Marshamarshal answered 30/7, 2021 at 21:35 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.