繁体   English   中英

如何对返回 livedata 的 function 进行单元测试

[英]how to unit test a function that returns livedata

在我的 viewModel 中,我有一个返回 liveData 的 function。 这个 function 在片段中被直接调用,因此在那里直接观察到。 我无法得到如何测试这个 function,因为在测试的情况下没有观察到 function 发出的 liveData,因此它不会返回值。

这是我的 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))
        }

    }

这就是我在片段中调用它的方式:

   viewModel.saveRating(rating, npsEventData?.eventName ?: "").observe(this, Observer {
      // on getting data
   })

提前致谢!

您需要有一个 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 块在任何官方 kotlin 或 Android 文档中均未提及,但测试异常会导致异常,而不是像我在这里所要求的那样通过测试。

我使用 testCoroutineDispatcher 作为调度程序的另一件事是不足以让某些测试通过,您需要将 coroutineScope 而不是调度程序注入 viewModel。

例如

fun throwExceptionInAScope(coroutineContext: CoroutineContext) {


    viewModelScope.launch(coroutineContext) {

        delay(2000)
        throw RuntimeException("Exception Occurred")
    }
}

你有一个像这样的 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)

    }

如果您使用 class MyViewModel(private val coroutineScope: CoroutineScope) ,它将通过

现在,让我们来看看如何使用异步任务测试 liveData。 我使用这个 class,Google 的LiveDataTestUtil class,用于同步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()
            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)
    }
}

现在,您可以像测试同步代码一样测试它

@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,它适用于暂停 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.

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM