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。
作者:HaroldGao
链接:https://juejin.cn/post/6956588138487775240
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。