【问题标题】:Android Kotlin - Unit test a delayed coroutine that invokes a lambda action when delay is finishedAndroid Kotlin - 对延迟完成时调用 lambda 动作的延迟协程进行单元测试
【发布时间】:2022-01-29 11:43:59
【问题描述】:

我已经为此苦苦挣扎了一段时间,也许有人可以帮助... 我在我的测试类中有这个功能:

fun launchForegroundTimer(context: Context) {
        helper.log("AppRate", "[$TAG] Launching foreground count down [10 seconds]")
        timerJob = helper.launchActionInMillisWithBundle(Dispatchers.Main, TimeUnit.SECOND.toMillis(10), context, this::showGoodPopupIfAllowed)
}

所以在那个函数中,我首先写入一些日志,然后调用一个协程函数,该函数需要一个 Dispatcher 参数,在运行动作之前等待多长时间,我想传递给动作的任何对象以及时间过去后调用的实际操作函数。

所以在这种情况下,类中的私有方法this::showGoodPopupIfAllowed 在经过 10,000 毫秒后被调用。 这是那个函数:

private fun showGoodPopupIfAllowed(context: Context?) {
        if (isAllowedToShowAppRate()) {
            showGoodPopup(context)
        }
}

在第一个 if 中,在我可以调用 showGoodPopup(context) 之前进行了一系列检查

现在,这里是helper.launchActionInMillisWithBundle 函数:

fun <T> launchActionInMillisWithBundle(dispatcher: CoroutineContext, inMillis: Long, bundle: T, action: (T) -> Unit): Job = CoroutineScope(dispatcher).launchInMillisWithBundle(inMillis, bundle, action)

这里是实际的 CoroutineScope 扩展函数:

fun <T> CoroutineScope.launchInMillisWithBundle(inMillisFromNow: Long, bundle: T, action: (T) -> Unit) = this.launch {
    delay(inMillisFromNow)
    action(bundle)
}

我想要实现的是一个 UnitTest,它调用 launchForegroundTimer 函数,使用适当的参数调用辅助函数,并继续调用 lambda showGoodPopupIfAllowed 函数,我还可以为所有isAllowedToShowAppRate 中出现的 IF 语句。

目前我的测试在调用launchActionInMillisWithBundle 后立即停止并且测试刚刚结束。我假设没有真正调用任何协程,因为我在嘲笑 helper 类...不知道如何继续。

我阅读了一些有趣的文章,但似乎没有一篇能解决这种状态。 我目前的测试功能是这样的:

    private val appRaterManagerHelperMock = mockkClass(AppRaterManagerHelper::class)
    private val timerJobMock = mockkClass(Job::class)
    private val contextMock = mockkClass(Context::class)

    @Test
    fun `launch foreground timer`() {
        every { appRaterManagerHelperMock.launchActionInMillisWithBundle(Dispatchers.Main, TimeUnit.SECOND.toMillis(10), contextMock, any()) } returns timerJobMock
        val appRaterManager = AppRaterManager(appRaterManagerHelperMock)
        appRaterManager.launchForegroundTimer(contextMock)

        verify(exactly = 1) { appRaterManagerHelperMock.log("AppRate", "[AppRaterManager] Launching foreground count down [10 seconds]") }
    }

我使用 mockk 作为我的 Mocking 库。 AppRaterManager 是被测类

我还想提一下,理论上我可以将协程调用移到被测类之外。所以像activity.onResume() 这样的外部类可以启动某种倒计时,然后直接调用一个检查showGoodPopupIfAllowed() 的函数。但是目前,请假设我没有任何方法可以更改调用代码,因此计时器和协程应该保留在测试域中的类中。

谢谢!

【问题讨论】:

    标签: android unit-testing kotlin lambda kotlin-coroutines


    【解决方案1】:

    好的,我在 https://mockk.io/#capturing 上更深入地了解了捕获/答案,发现有一个 capture 函数。 所以我在slot 中捕获了 lambda 函数,这使我能够调用 lambda,然后实际代码在被测类中继续。我可以从那里嘲笑其余的行为。

    这是我对这种情况的测试功能(适用于任何卡住的人):

        @Test
        fun `launch foreground timer, not participating, not showing good popup`() {
            val slot = slot<(Context) -> Unit>()
            every { appRaterManagerHelperMock.launchActionInMillisWithBundle(Dispatchers.Main, TimeUnit.SECOND.toMillis(10), contextMock, capture(slot)) } answers {
                slot.captured.invoke(contextMock)
                timerJobMock
            }
    
            every { appRaterManagerHelperMock.isParticipating() } returns false
    
            val appRaterManager = AppRaterManager(appRaterManagerHelperMock)
            appRaterManager.launchForegroundTimer(contextMock)
    
            verify(exactly = 1) { appRaterManagerHelperMock.log("AppRate", "[AppRaterManager] Launching foreground count down [10 seconds]") }
            verify(exactly = 1) { appRaterManagerHelperMock.isParticipating() }
            verify(exactly = 0) { appRaterManagerHelperMock.showGoodPopup(contextMock, appRaterManager) }
        }
    

    那么现在剩下的就是如何在提供的延迟时间结束后测试协程是否实际调用了 lambda。

    【讨论】:

      猜你喜欢
      • 2018-04-20
      • 2019-04-15
      • 2019-09-16
      • 2019-01-15
      • 1970-01-01
      • 1970-01-01
      • 2021-06-27
      • 2016-07-08
      • 1970-01-01
      相关资源
      最近更新 更多