简体   繁体   English

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

[英]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.

相关问题 如何使用 Kotest 和 Mockk 库为返回 LiveData 的方法编写单元测试 - How to write unit test for the method returns LiveData with Kotest and Mockk library 如何对 LiveData 转换进行单元测试 - How to unit test LiveData Transformations 如何使用 livedata 对协程进行单元测试 - How to unit test coruotine with livedata 如何在单元测试中使用协程测试实时数据 - How to test livedata with coroutine in unit test 使用 livedata 进行 Android 视图模型单元测试 - Android viewmodel unit test with livedata 如何使用带有 runTest 的 Flows 和 LiveData 对 ViewModel 进行单元测试? - How to Unit Test a ViewModel using Flows and LiveData with runTest? 如何在 viewModel 中测试从存储库返回 LiveData 值的方法 - How to test a method in viewModel which returns LiveData value from repository 使用 RxJava 时对 LiveData 进行本地单元测试 - Local unit test for LiveData while using RxJava 使用存储库模式和 LiveData 对 ViewModel 进行单元测试 - Unit test ViewModel using repository pattern and LiveData Android 单元测试:如何模拟包含 MutableLiveData 但仅公开 LiveData 的 object? - Android Unit Test: How to mock an object which contains MutableLiveData but only exposes LiveData?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM