LiveData 单元测试

本文参考自 Unit-testing LiveData and other common observability problems

参考 Google 代码官方测试代码 here

==单元测试时,LiveData.value 返回 null==

    @Test
    @Throws(Exception::class)
    fun testLiveDataFail() = runBlocking {
        meditationDao.insert(MeditationTrip())
        val trips = meditationDao.getAllTrips()
        assertEquals(1, trips.value!!.size) // NullPointerException
    }
复制代码

首先,Transformations#map 得到的 LiveData 必须有观察者,才会在原始 LiveData 更新时调用 map 函数更新值。理解起来也很合理,没有人观察的值没有必要被实时更新。实现原理是,Transformations#map 方法将 LiveData 转化为 MediatorLiveData,最终通过 LiveData#observeForever 向原始的 LiveData 添加一个 AlwaysActiveObserver,但是前提是这个 MediatorLiveData 必须要有 active 观察者(androidx.lifecycle.MediatorLiveData#addSource)。

Room 库中为 DAO 注解生成的实现类,返回的 LiveData 是 androidx.room.RoomTrackingLiveData 类型,类似地也只有在有 active Observer 的前提下,才会在数据库表更新时,执行查询语句,更新 value。因为没有观察者时,没必要更新。实现原理是在 RoomTrackingLiveData 第一次添加 Obeserver 时(OnActive),往 RoomDatabase 的 InvalidationTracker 中添加 WeakObserver,这样当数据库发生变化时,就会通知这些 Observer(androidx.room.InvalidationTracker#addWeakObserver)

以上问题的原因都是因为没有 active Observer,解决办法:

fun <T> LiveData<T>.getOrWaitValue(
        time: Long = 2,
        timeUnit: TimeUnit = TimeUnit.SECONDS,
        afterObserve: () -> Unit = {}
): T {
    var data: T? = null
    val latch = CountDownLatch(1)
    val observer = object : Observer<T> {
        override fun onChanged(t: T) {
            data = t
            latch.countDown()
            this@getOrWaitValue.removeObserver(this) // 添加了观察者
        }
    }
    this.observeForever(observer)
    afterObserve.invoke()

    // wait for short time
    if (!latch.await(time, timeUnit)) {
        this.removeObserver(observer)
        throw TimeoutException("LiveData value is never set!")
    }

    @Suppress("unchecked_cast")
    return data as T
}
复制代码

单元测试时,又报错:

java.lang.IllegalStateException: Cannot invoke observeForever on a background thread
	at androidx.lifecycle.LiveData.assertMainThread(LiveData.java:487)
	at androidx.lifecycle.LiveData.observeForever(LiveData.java:224)
复制代码

这是因为 LiveData 注册 Observer 时,要求必须是在主线程,通过 ArchTaskExecutor.getInstance().isMainThread() 来判断。

解决办法是为单元测试添加 InstantTaskExecutorRule:

    @Rule
    @JvmField
    val instantExecutorRule = InstantTaskExecutorRule()
复制代码

InstantTaskExecutorRule 作为 TestWatcher 的子类,会在单元测试开始前,替换 Archtechture Component 的后台执行器 ArchTaskExecutor,每个任务都是同步运行(runnable#run),isMainThread 返回 true。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享