【发布时间】:2019-11-23 10:54:03
【问题描述】:
我想测试我的数据库层,但我发现自己陷入了一种 catch-22 类型的情况。
测试用例由两部分组成:
- 保存一些实体
- 加载实体并断言数据库映射按预期工作
简而言之,问题在于:
-
Insert是一个suspend方法,这意味着它需要在runBlocking{}中运行 -
Query返回结果的LiveData,这也是异步的。因此需要观察。 this SO 问题解释了如何做到这一点。 - 但是,为了根据上面的链接观察 LiveData,我必须使用
InstantTaskExecutorRule。 (否则我会得到java.lang.IllegalStateException: Cannot invoke observeForever on a background thread.) -
这适用于大多数情况,但不适用于
@Transaction-annotated DAO 方法。测试永远不会完成。我认为它在等待某个事务线程时陷入僵局。 - 删除
InstantTaskExecutorRule让 Transaction-Insert 方法完成,但我无法断言其结果,因为我需要规则才能观察数据。
详细说明
我的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
}
}
getValueBlocking 是LiveData 的扩展函数,几乎从上面的链接复制粘贴
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
}
测试这种情况的正确方法是什么?在开发数据库映射层时,我需要这些类型的测试,以确保一切都按预期工作。
【问题讨论】:
-
这是使用实时数据和协程测试房间的唯一方法。很快谷歌将发布新的测试库来解决这些问题。
-
听到这个消息很难过。你碰巧有一个链接到他们说他们会解决这个问题的地方吗?
-
github.com/googlesamples/android-architecture-components/blob/… 这是用于测试实时数据的谷歌示例代码。
标签: android testing kotlin android-room kotlin-coroutines