0

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:

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>

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.

Jun Du
  • 29
  • 4

1 Answers1

0

Finally, I got the answer at Google Sample! First create the extension methods for 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!!

Jun Du
  • 29
  • 4
  • Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Nov 10 '21 at 04:28