在我的viewModel中,我有一个返回liveData的函数。该功能直接在片段中调用,因此可以直接在片段中观察到。我无法获得如何测试此功能的信息,因为在测试的情况下未观察到该功能发出的liveData,因此它不会返回该值。
这是我的功能,我想为以下对象编写测试:
fun saveRating(rating: Float, eventName: String): LiveData<Response<SaveRatingData?>?> {
val request = RatingRequest(rating.toDouble(), eventName, false)
return liveData(viewModelScope.coroutineContext + Dispatchers.IO) {
emit(repository.saveRatings(request))
}
}
这就是我在一个片段中称呼它的方式:
viewModel.saveRating(rating, npsEventData?.eventName ?: "").observe(this, Observer {
// on getting data
})
提前感谢!
您需要具有testCoroutineDispatcher或testCoroutineScope才能将viewModel的范围设置为测试范围。
class TestCoroutineRule : TestRule {
private val testCoroutineDispatcher = TestCoroutineDispatcher()
val testCoroutineScope = TestCoroutineScope(testCoroutineDispatcher)
override fun apply(base: Statement, description: Description?) = object : Statement() {
@Throws(Throwable::class)
override fun evaluate() {
Dispatchers.setMain(testCoroutineDispatcher)
base.evaluate()
Dispatchers.resetMain()
try {
testCoroutineScope.cleanupTestCoroutines()
} catch (exception: Exception) {
exception.printStackTrace()
}
}
}
fun runBlockingTest(block: suspend TestCoroutineScope.() -> Unit) =
testCoroutineScope.runBlockingTest { block() }
}
[try-catch块在任何正式的kotlin或Android文档中均未提及,但是测试异常会导致异常,而不是按照我在此here中的要求通过测试。
而且我在用testCoroutineDispatcher作为调度程序时遇到的另一件事不足以通过某些测试,您需要将coroutineScope而不是调度程序注入viewModel。
例如
fun throwExceptionInAScope(coroutineContext: CoroutineContext) {
viewModelScope.launch(coroutineContext) {
delay(2000)
throw RuntimeException("Exception Occurred")
}
}
您具有像这样的函数,它将引发异常,并且将testCoroutineContext传递给该测试,它将失败。
@Test(expected = RuntimeException::class)
fun `Test function that throws exception`() =
testCoroutineDispatcher.runBlockingTest {
// 🔥 Using testCoroutineDispatcher causes this test to FAIL
viewModel.throwExceptionInAScope(testCoroutineDispatcher.coroutineContext)
// 🔥 This one passes since we use context of current coroutineScope
viewModel.throwExceptionInAScope(this.coroutineContext)
}
如果使用类MyViewModel(private val coroutineScope: CoroutineScope)
,则通过
现在,让我们了解如何使用异步任务测试liveData。我使用Google的LiveDataTestUtil
类来同步liveData
和
@get:Rule
var instantTaskExecutorRule = InstantTaskExecutorRule()
通常]
fun <T> LiveData<T>.getOrAwaitValue( 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(o: T?) { data = o latch.countDown() [email protected](this) } } this.observeForever(observer) afterObserve.invoke() // Don't wait indefinitely if the LiveData is not set. if (!latch.await(time, timeUnit)) { this.removeObserver(observer) throw TimeoutException("LiveData value was never set.") } @Suppress("UNCHECKED_CAST") return data as T } /** * Observes a [LiveData] until the `block` is done executing. */ fun <T> LiveData<T>.observeForTesting(block: () -> Unit) { val observer = Observer<T> { } try { observeForever(observer) block() } finally { removeObserver(observer) } }
现在,您可以像测试同步代码一样对其进行测试
@Test fun `Given repo saves response, it should return the correct one` = testCoroutineScope.runBlockingTest { // GIVEN val repository = mockk<<Repository>() val actual = Response(...) coEvery { repository.saveRatings } returns actual // WHEN val expected = viewModel.saveResponse() // THEN Truth.assertThat(actual).isEqualTo(expected) }
我使用了mockK,它可以很好地暂停模拟。
此外,如果您进行了改造或Room函数调用,则不需要使用Dispatchers.IO
;如果您除了进行改造或Room操作之外,没有执行其他任务,它们将使用自己的带有suspend修饰符的线程。