[英]how to unit test a function that returns livedata
In my viewModel, I have a function that returns liveData.在我的 viewModel 中,我有一个返回 liveData 的 function。 This function is directly called in the fragment and hence it is observed directly there.这个 function 在片段中被直接调用,因此在那里直接观察到。 I am not able to get how can I test this function since liveData emitted by the function is not getting observed in case of tests and hence it will not return the value.我无法得到如何测试这个 function,因为在测试的情况下没有观察到 function 发出的 liveData,因此它不会返回值。
This is my function, I want to write test for:这是我的 function,我想写测试:
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))
}
}
and this is how I am calling it in a fragment:这就是我在片段中调用它的方式:
viewModel.saveRating(rating, npsEventData?.eventName ?: "").observe(this, Observer {
// on getting data
})
Thanks in advance!提前致谢!
You need to have a testCoroutineDispatcher or testCoroutineScope to be able to set scope of your viewModel to scope of testing.您需要有一个 testCoroutineDispatcher 或 testCoroutineScope 才能将您的 viewModel 的 scope 设置为测试的 scope。
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 block is not mentioned in any official kotlin or Android documents but testing exceptions cause exceptions instead of passing the test as i asked in this here . Try-catch 块在任何官方 kotlin 或 Android 文档中均未提及,但测试异常会导致异常,而不是像我在这里所要求的那样通过测试。
And another thing i experienced with testCoroutineDispatcher as dispatcher is not enough for some test to pass, you need to inject coroutineScope instead of dispatcher to viewModel.我使用 testCoroutineDispatcher 作为调度程序的另一件事是不足以让某些测试通过,您需要将 coroutineScope 而不是调度程序注入 viewModel。
For instance例如
fun throwExceptionInAScope(coroutineContext: CoroutineContext) {
viewModelScope.launch(coroutineContext) {
delay(2000)
throw RuntimeException("Exception Occurred")
}
}
You have a function like this that throws exception and you pass testCoroutineContext to this test it fails.你有一个像这样的 function 抛出异常,你将 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)
}
It passes if you use class MyViewModel(private val coroutineScope: CoroutineScope)
如果您使用 class MyViewModel(private val coroutineScope: CoroutineScope)
,它将通过
Now, let's get to how to test liveData with asynchronous tasks.现在,让我们来看看如何使用异步任务测试 liveData。 I use this class, Google's LiveDataTestUtil
class, for synching liveData
and我使用这个 class,Google 的LiveDataTestUtil
class,用于同步liveData
和
@get:Rule
var instantTaskExecutorRule = InstantTaskExecutorRule()
as rule作为规则
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()
this@getOrAwaitValue.removeObserver(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)
}
}
Now, you can test it same as you test synchronous code现在,您可以像测试同步代码一样测试它
@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)
}
I used mockK, which works well with suspending mocking.我使用了 mockK,它适用于暂停 mocking。
Also you don't need to use Dispatchers.IO
if you have retrofit or Room function calls, they use their own thread with suspend modifier if you are not doing other task than retrofit or room actions. Also you don't need to use Dispatchers.IO
if you have retrofit or Room function calls, they use their own thread with suspend modifier if you are not doing other task than retrofit or room actions.
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.