如何使用嵌套挂起函数对 kotlin 协程进行单元测试

Omk*_*kar 7 android unit-testing coroutine kotlin kotlinx.coroutines

下面是我试图进行单元测试的代码片段。第一个挂起函数之前的代码async(CommonPool)是可测试的,但在那之后,测试不断失败。我尝试使用runBlocking但无论如何都无法测试嵌套的挂起异步函数。

interface Listener {
        fun onLoading(user: User?)

        fun onSuccess(user: User)
}

execute(listener: Listener) {

        listener.onLoading(null)
        val service = UserService.getInstance(context, "someurl")
        val database = UserDatabase.getInstance(context)

        launch(UI) {

            val user = async(CommonPool) {
                userDatabase.getUser()
            }.await()

            listener.onLoading(user)

            val response = service.getUsersSelf(oauthToken).await()

            async(CommonPool) {database.saveUser(userResponse.data.user)}.await()

            val user = async(CommonPool) {database.getUser()}.await()

            listener.onSuccess(user)
        }
    }
Run Code Online (Sandbox Code Playgroud)

下面是我的单元测试,我使用mockito来模拟我的听众并检查交互的数量。

@Test
    fun execute() {
        runBlocking {
            userDatabase.saveUser(user)

            val listener = mock(GetUser.Listener::class.java)

            getUser.execute(listener)

            verify(listener, times(1)).onLoading(null) // Success

            verify(listener, times(1)).onLoading(user) // Fails

            verify(listener, times(1)).onSuccess(user) // Fails
        }
    }
Run Code Online (Sandbox Code Playgroud)

但最后两次verify测试失败了。有人可以帮我测试吗?

Mar*_*nik 1

您的execute功能是一劳永逸的。如果这是通过线程完成的,则将Thread { <code> }.start()完全放弃对线程的引用。您唯一剩下的就是Listener在调用完成时收到通知的实例,但在发生错误时则不会收到通知。

虽然我怀疑这是使用协程的好方法,因为协程都是为了消除像您的 那样的回调接口Listener,但如果您知道这种方法对您有好处,那么侦听器就是您启动的协程中发生的事件的唯一句柄。

因此,您必须在侦听器内实现单元测试逻辑。由于没有失败回调,您可能需要等待一段有限的时间才能调用方法。一种方法:

fun execute() {
    val latch = CountDownLatch(3)
    runBlocking {
        val x = object : Listener {

            override fun onLoading(user: User?) {
                if (user == null) {
                    require(latch.count == 3L)
                    latch.countDown()
                } else {
                    require(latch.count == 2L)
                    latch.countDown()
                }
            }

            override fun onSuccess(user: User) {
                require (latch.count == 1L)
                latch.countDown()
            }
        }
    }
    require(latch.await(10, TimeUnit.SECONDS))
}
Run Code Online (Sandbox Code Playgroud)