简体   繁体   English

如何使用 Kotest 和 Mockk 库为返回 LiveData 的方法编写单元测试

[英]How to write unit test for the method returns LiveData with Kotest and Mockk library

I am using MVVM to architecutre my android app, my repository has a method which query data from Room Database and returns a LiveData, the signure of my method is:我正在使用 MVVM 来架构我的 android 应用程序,我的存储库有一个方法可以从 Room 数据库中查询数据并返回一个 LiveData,我的方法的签名是:

fun getFolder(id: Long): LiveData<Folder?>

I want to write a unit test for this method with the following code:我想使用以下代码为此方法编写单元测试:

import androidx.lifecycle.MutableLiveData
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe
import io.mockk.every
import io.mockk.mockk
import io.mockk.verify
import my.package.Folder
import my.package.FolderRepository
import java.util.*

class FolderRepositoryTest: FunSpec({

    val repository = mockk<FolderRepository>()
    val folder = Folder(
      // folder field init code    
    )
    val folderLiveData = MutableLiveData(folder)


    test("FolderRepository getFolder works as expected") {
        val id = folder.id.toLong()
        every {  repository.getFolder(any()) } returns folderLiveData
        repository.getFolder(id)
        verify {
            repository.getFolder(id)
        } shouldBe folderLiveData
    }
    
})

But the test failed wit the following failure message.但是测试失败并显示以下失败消息。

io.kotest.assertions.AssertionFailedError: expected:androidx.lifecycle.MutableLiveData@1afc7182 but was:<kotlin.Unit> io.kotest.assertions.AssertionFailedError:预期:androidx.lifecycle.MutableLiveData@1afc7182 但是是:<kotlin.Unit>

expected:<androidx.lifecycle.MutableLiveData@1afc7182> but was:<kotlin.Unit>
Expected :androidx.lifecycle.MutableLiveData@1afc7182
Actual   :kotlin.Unit

Can anybody help me point out where I am wrong and how to write unit test cases with kotest library and Mockk library.任何人都可以帮我指出我错在哪里以及如何使用kotest库和Mockk库编写单元测试用例。

Finally, I got the answer at Google Sample !最后,我在Google Sample得到了答案! First create the extension methods for LiveData.首先为 LiveData 创建扩展方法。

fun <T> LiveData<T>.getOrAwaitValue(
        time: Long = 2,
        timeUnit: TimeUnit = TimeUnit.SECONDS,
        afterObserver: () -> Unit = {}
): T {
    var data: T? = null
    val latch = CountDownLatch(1)
    val observer = object: Observer<T> {
        override fun onChanged(t: T) {
            data = t
            latch.countDown()
            this@getOrAwaitValue.removeObserver(this)
        }
    }
    this.observeForever(observer)
    afterObserver.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
}

fun <T> LiveData<T>.observeForTesting(block: () -> Unit) {
    val observer = Observer<T> { }
    try {
        observeForever(observer)
        block()
    } finally {
        removeObserver(observer)
    }
}

Second, create a test config for your app其次,为您的应用创建测试配置

import androidx.arch.core.executor.ArchTaskExecutor
import androidx.arch.core.executor.TaskExecutor
import io.kotest.core.config.AbstractProjectConfig
import kotlinx.coroutines.test.TestCoroutineDispatcher
// This is necessary, otherwise you will got Looper not mocked failed.
object TestConfig: AbstractProjectConfig() {
    private val testDispatcher = TestCoroutineDispatcher()

    override suspend fun beforeProject() {
        super.beforeProject()
        setupLiveData()
    }

    override suspend fun afterProject() {
        super.afterProject()
        resetLiveData()
    }

    private fun setupLiveData() {
        ArchTaskExecutor.getInstance().setDelegate(object : TaskExecutor() {
            override fun executeOnDiskIO(runnable: Runnable) {
                runnable.run()
            }

            override fun postToMainThread(runnable: Runnable) {
                runnable.run()
            }

            override fun isMainThread(): Boolean {
                return true
            }
        })
    }

    private fun resetLiveData() {
        ArchTaskExecutor.getInstance().setDelegate(null)
    }
}

And at last, I changed my code a bit and the test passed, here is the code最后,我稍微改变了我的代码并且测试通过了,这是代码

test("FolderRepository should works as expected")
    .config(testCoroutineDispatcher = true) {
            val id = folder.id.toLong()
            val result = MutableLiveData(folder)
            every { folderRepository.getFolder(any()) } returns result
            val f = folderRepository.getFolder(id).getOrAwaitValue()
            f shouldBe result.value
            verify { folderRepository.getFolder(withArg {
                assertTrue(it == id)
            }) }
            verify { folderRepository.getFolder(id) }
        }

Maybe there is better solution, hope you can post an answer and let's improve our code!!也许有更好的解决方案,希望您可以发布答案,让我们改进我们的代码!!

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

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