使用LiveData,协程和事务测试Android Room [英] Testing Android Room with LiveData, Coroutines and Transactions

查看:291
本文介绍了使用LiveData,协程和事务测试Android Room的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想测试我的数据库层,我陷入了22类情况.

I want to test my database layer and I have caught myself in a catch-22 type of a situation.

测试用例由两部分组成:

  • 保存一些实体
  • 加载实体并断言数据库映射按预期工作

总而言之,问题在于:

  • Insertsuspend方法,这意味着它需要在runBlocking{}
  • 中运行
  • Query返回结果的LiveData,该结果也是异步的.因此,需要对其进行观察.有一个 SO问题,说明了如何执行此操作.
  • 为了根据上面的链接观察LiveData,我必须使用InstantTaskExecutorRule. (否则我会得到java.lang.IllegalStateException: Cannot invoke observeForever on a background thread.)
  • 这适用于大多数情况,但不适用于@Transaction注释的DAO方法.测试永远不会结束.我认为它在等待某些事务线程时陷入僵局.
  • 删除InstantTaskExecutorRule可以使Transaction-Insert方法完成,但是我不能断言其结果,因为我需要规则才能观察数据.
  • Insert is a suspend method, which means it needs to be run in runBlocking{}
  • Query returns a LiveData of the result, which is also asynchronous. Therefore it needs to be observed. There's this SO question that explains how to do that.
  • In order to observe the LiveData according to the above link, however, I must use the InstantTaskExecutorRule. (Otherwise I get java.lang.IllegalStateException: Cannot invoke observeForever on a background thread.)
  • This works for most of the cases, but it does not work with @Transaction-annotated DAO methods. The test never finishes. I think it's deadlocked on waiting for some transaction thread.
  • Removing the InstantTaskExecutorRule lets the Transaction-Insert method finish, but then I am not able to assert its results, because I need the rule to be able to observe the data.

我的Dao类如下:

@Dao
interface GameDao {
    @Query("SELECT * FROM game")
    fun getAll(): LiveData<List<Game>>

    @Insert
    suspend fun insert(game: Game): Long

    @Insert
    suspend fun insertRound(round: RoundRoom)

    @Transaction
    suspend fun insertGameAndRounds(game: Game, rounds: List<RoundRoom>) {
        val gameId = insert(game)
        rounds.onEach {
            it.gameId = gameId
        }

        rounds.forEach {
            insertRound(it)
        }
    }

测试用例是:

@RunWith(AndroidJUnit4::class)
class RoomTest {
    private lateinit var gameDao: GameDao
    private lateinit var db: AppDatabase

    @get:Rule
    val instantTaskExecutorRule = InstantTaskExecutorRule()

    @Before
    fun createDb() {
        val context = ApplicationProvider.getApplicationContext<Context>()
        db = Room.inMemoryDatabaseBuilder(
            context, AppDatabase::class.java
        ).build()
        gameDao = db.gameDao()
    }

    @Test
    @Throws(Exception::class)
    fun storeAndReadGame() {
        val game = Game(...)

        runBlocking {
            gameDao.insert(game)
        }

        val allGames = gameDao.getAll()

        // the .getValueBlocking cannot be run on the background thread - needs the InstantTaskExecutorRule
        val result = allGames.getValueBlocking() ?: throw InvalidObjectException("null returned as games")

        // some assertions about the result here
    }

    @Test
    fun storeAndReadGameLinkedWithRound() {
        val game = Game(...)

        val rounds = listOf(
            Round(...),
            Round(...),
            Round(...)
        )

        runBlocking {
            // This is where the execution freezes when InstantTaskExecutorRule is used
            gameDao.insertGameAndRounds(game, rounds)
        }

        // retrieve the data, assert on it, etc
    }
}

getValueBlockingLiveData的扩展功能,几乎是从上面的链接复制粘贴的

The getValueBlocking is an extension function for LiveData, pretty much copypasted from the link above

fun <T> LiveData<T>.getValueBlocking(): T? {
    var value: T? = null
    val latch = CountDownLatch(1)

    val observer = Observer<T> { t ->
        value = t
        latch.countDown()
    }

    observeForever(observer)

    latch.await(2, TimeUnit.SECONDS)
    return value
}

测试此方案的正确方法是什么?在开发数据库映射层时,我需要进行这些类型的测试,以确保一切正常.

What's the proper way to test this scenario? I need these types of tests while developing the database mapping layer to make sure everything works as I expect.

推荐答案

现在有解决此问题的方法,在

There is now a solution to this issue, explained in this answer.

此修复程序将单个行添加到房间"内存数据库构建器:

The fix is adding a single line to the Room in-memory database builder:

db = Room
    .inMemoryDatabaseBuilder(context, AppDatabase::class.java)
    .setTransactionExecutor(Executors.newSingleThreadExecutor()) // <-- this makes all the difference
    .build()

使用单线程执行程序,测试将按预期工作.

With the single thread executor the tests are working as expected.

这篇关于使用LiveData,协程和事务测试Android Room的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆