如何对Kotlin暂停功能进行单元测试

时间:2018-04-16 19:42:05

标签: unit-testing kotlin mockito mvp coroutine

我遵循MVP模式+ UseCases与Model层进行交互。这是我想测试的Presenter中的一种方法:

fun loadPreviews() {
    launch(UI) {
        val items = previewsUseCase.getPreviews() // a suspending function
        println("[method] UseCase items: $items")

        println("[method] View call")
        view.showPreviews(items)
    }
}

我的简单BDD测试:

fun <T> givenSuspended(block: suspend () -> T) = BDDMockito.given(runBlocking { block() })

infix fun <T> BDDMockito.BDDMyOngoingStubbing<T>.willReturn(block: () -> T) = willReturn(block())

@Test
fun `load previews`() {
    // UseCase and View are mocked in a `setUp` method

    val items = listOf<PreviewItem>()
    givenSuspended { previewsUseCase.getPreviews() } willReturn { items }

    println("[test] before Presenter call")
    runBlocking { presenter.loadPreviews() }
    println("[test] after Presenter call")

    println("[test] verify the View")
    verify(view).showPreviews(items)
}

测试成功通过,但日志中有些奇怪。我希望它是:

  • “Presenter之前的[测试]”
  • “[方法] UseCase项目:[]”
  • “[方法]查看电话”
  • “Presenter致电后的[测试]”
  • “[test]验证查看”

但事实证明是:

    在演示者致电之前
  • [测试]
  • [测试]在Presenter调用
  • 之后
  • [test]验证查看
  • [method] UseCase项目:[]
  • [方法]查看电话

这种行为的原因是什么?我应该如何解决?

2 个答案:

答案 0 :(得分:2)

我发现这是因为CoroutineDispatcher。我曾经用UI模仿EmptyCoroutineContext上下文。切换到Unconfined已解决了问题

答案 1 :(得分:2)

我看到你发现了自己,但我想为可能遇到同样问题的人解释一下

当您执行launch(UI) {}时,会创建一个新的协程并将其分派到“UI”调度程序,这意味着您的协程现在在另一个线程上运行。

您的runBlocking{}调用会创建一个新的协同程序,但runBlocking{}将等待此协程在继续之前结束,您的loadPreviews()函数会创建一个协程,启动它然后立即返回,所以runBlocking()等待它并返回。

因此,当runBlocking{}返回时,您使用launch(UI){}创建的协程仍然在另一个线程中运行,这就是为什么日志的顺序搞砸了

Unconfined上下文是一个特殊的CoroutineContext,它只是创建一个在当前线程上执行协同程序的调度程序,所以现在当你执行runBlocking{}时,它必须等待由launch{}创建的协程结束,因为它在同一个线程上运行,从而阻塞了该线程。

我希望我的解释清楚,祝你有个美好的一天