如何对返回实时数据的函数进行单元测试

问题描述 投票:0回答:1

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

提前感谢!

android unit-testing android-livedata kotlin-coroutines android-viewmodel
1个回答
1
投票

您需要具有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修饰符的线程。

© www.soinside.com 2019 - 2024. All rights reserved.