如何为该方法编写单元测试返回带有Kotest和Mockk库的LiveData



我正在使用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.assetions.AssertionFailedError:应为:androidx.lifecycle.MutableLiveData@1afc7182但是是:<科特林。单位>

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

有人能帮我指出我错在哪里,以及如何使用kotest库和Mock库来编写单元测试用例吗。

最后,我在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) }
}

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