0

我正在使用 MVVM 来构建我的 android 应用程序,我的存储库有一个从房间数据库查询数据并返回 LiveData 的方法,我的方法的签名是:

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

我想用下面的代码为这个方法写一个单元测试:

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
    }
    
})

但是测试失败并显示以下失败消息。

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

谁能帮我指出我错在哪里以及如何使用kotest库和Mockk库编写单元测试用例。

4

1 回答 1

0

最后,我在Google Sample上得到了答案!首先为 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)
    }
}

其次,为您的应用创建一个测试配置

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)
    }
}

最后,我稍微修改了一下代码,测试通过了,代码如下

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) }
        }

也许有更好的解决方案,希望您能发布答案,让我们改进我们的代码!

于 2021-11-10T02:20:07.750 回答