10

I'm testing with AS 3.4.1 and Emulator running Android 9.

The following test won't run, when I use a Room Dao Function annotated with @Transaction in it.

class RecurrenceManagerTest : DatabaseTest() {

    @Rule
    @JvmField
    val instantTaskExecutorRule = InstantTaskExecutorRule()

    var recurringEntryId: Long = -1

    @Before
    override fun setup() {
        super.setup() // only initialized the db

        val recurringEntry = RecurringEntry(
            recurrence = Recurrence(DATE.toEpochMilli(), Recurrence.DAILY)
        )

        recurringEntryId = runBlocking { db.recurringEntryDao().insert(recurringEntry) }

        val recurringBookEntry = BookEntry.create(
            title = TITLE,
            date = DATE,
            value = VALUE,
            isPaid = IS_PAID,
            notes = NOTES,
            entryType = ENTRY_TYPE,
            categoryId = CATEGORY_ID,
            contacts = CONTACTS,
            recurringEntryId = recurringEntryId
        )

        runBlocking {
            db.bookEntryDao().insert(recurringBookEntry) // BreakPoint #1
        }
    }

    @Test
    fun testInsertRecurrencesAndSchedule() { 
        var recurringEntry = runBlocking { db.recurringEntryDao().get(recurringEntryId) } // BreakPoint #2

        assertThat(recurringEntry, notNullValue())

        runBlocking { RecurrenceManager.insertRecurrencesAndSchedule(ApplicationProvider.getApplicationContext(), db, recurringEntry!!) }

        val bookEntries = db.bookEntryDao().getBookEntries().liveDataValue()
    }
}

This is the function for inserting:

@Transaction
suspend fun insert(bookEntry: BookEntry): Long {
    val id = insert(bookEntry.entity)
    bookEntry.embeddedContacts?.apply {
        forEach {
            it.id = 0
            it.bookEntryId = id
        }
    }?.let {
        insert(it)
    }

    return id
}

So if I'm running the test like it is (see BreakPoint #1), BreakPoint #2 won't even be called, so the test ends somewhere before, without a result.

If I'm replacing the code at BreakPoint #1 with exact the same code, the insert function has, the test runs correctly.

Does anyone have an idea what's the issue here?

the_dani
  • 2,466
  • 2
  • 20
  • 46
  • 1
    That's because you are assering without the runblocking has finished – coroutineDispatcher May 30 '19 at 14:31
  • @Stavro Xhardha I thought runblocking waits until the body has finished? Any way to solve this? – the_dani Jun 02 '19 at 20:08
  • well I don't really know the way you are using. But check my post on the last section I am testing a room insert and select. check [this](https://link.medium.com/oTqhCh3wcX) – coroutineDispatcher Jun 02 '19 at 20:53
  • Answers on this related question by me suggest that this is not possible, at the moment :( https://stackoverflow.com/questions/57027850/testing-android-room-with-livedata-coroutines-and-transactions – Martin Melka Aug 19 '19 at 09:41

2 Answers2

27

You can use setTransactionExecutor to run transaction in another thread

return Room
       .inMemoryDatabaseBuilder(context, MyRoomDatabase::class.java)
       .setTransactionExecutor(Executors.newSingleThreadExecutor())
       .build()

then while testing use runBlocking instead of runBlockingTest

@Test
fun moveItem() = runBlocking {
    transactionFunction()
}
sumit matta
  • 744
  • 7
  • 8
6

I faced the same issue and the problem was because of the InstantTaskExecutorRule, if you remove the below block of code the @Transaction should work fine with the suspend keyword

@Rule
@JvmField
val instantTaskExecutorRule = InstantTaskExecutorRule()

It seems that this rule blocks the RoomDatabase from acquiring the transaction thread. In RoomDatabase.kt execution gets blocked in the below function:

private suspend fun Executor.acquireTransactionThread(controlJob: Job): ContinuationInterceptor

Hope this helps!

RealityExpander
  • 133
  • 1
  • 8
Hakem Zaied
  • 14,061
  • 1
  • 23
  • 25
  • 1
    Well, the test runs now, but I need the `InstantTaskExecutorRule` for the `liveDataValue()` method, because after removing this line, an error is thrown: `java.lang.IllegalStateException: Cannot invoke observeForever on a background thread`. – the_dani Jun 09 '19 at 18:46
  • 1
    How did you get it working with `InstantTaskExecutorRule `? – Hakem Zaied Jun 09 '19 at 18:52
  • The test runs, after removing InstantTaskExecutorRule, but now I've an error with observeForever. – the_dani Jun 09 '19 at 20:25
  • My `.liveDataValue()` extension function is a copy of this one: https://github.com/googlesamples/android-architecture-components/blob/master/GithubBrowserSample/app/src/test-common/java/com/android/example/github/util/LiveDataTestUtil.kt – the_dani Jun 09 '19 at 21:08
  • 1
    Is there a possibility to test LiveData + Coroutines? – the_dani Jun 12 '19 at 19:17
  • I not sure to be honest. But I wasn't able to get it working – Hakem Zaied Jun 15 '19 at 22:04