Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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"
like image 953
heronsanches Avatar asked May 07 '20 02:05

heronsanches


2 Answers

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.

like image 125
nglauber Avatar answered Oct 18 '22 00:10

nglauber


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))
        }
    }
like image 2
alisonthemonster Avatar answered Oct 17 '22 22:10

alisonthemonster