android - UI 线程上的单元测试协程

标签 android unit-testing kotlin kotlinx.coroutines

我正在使用协同程序对拉动进行异步调用以进行刷新,如下所示:

class DataFragment : Fragment(), SwipeRefreshLayout.OnRefreshListener {

    // other functions here

    override fun onRefresh() {
        loadDataAsync()
    }

    private fun loadDataAsync() = async(UI) {
        swipeRefreshLayout?.isRefreshing = true
        progressLayout?.showContent()

        val data = async(CommonPool) {
            service?.getData() // suspending function
        }.await()

        when {
            data == null -> showError()
            data.isEmpty() -> progressLayout?.showEmpty(null, parentActivity?.getString(R.string.no_data), null)
            else -> {
                dataAdapter?.updateData(data)
                dataAdapter?.notifyDataSetChanged()
                progressLayout?.showContent()
            }
        }

        swipeRefreshLayout?.isRefreshing = false
    }
}

当我实际将其安装到设备上时,一切正常。我的error、empty、data状态都处理的很好,性能不错。但是,我也在尝试使用 Spek 对其进行单元测试。我的 Spek 测试如下所示:

@RunWith(JUnitPlatform::class)
class DataFragmentTest : Spek({

    describe("The DataFragment") {

        var uut: DataFragment? = null

        beforeEachTest {
            uut = DataFragment()
        }

        // test other functions

        describe("when onRefresh") {
            beforeEachTest {
                uut?.swipeRefreshLayout = mock()
                uut?.onRefresh()
            }

            it("sets swipeRefreshLayout.isRefreshing to true") {
                verify(uut?.swipeRefreshLayout)?.isRefreshing = true // says no interaction with mock
            }
        }
    }           
}

测试失败,因为它表示没有与 uut?.swipeRefreshLayout 模拟交互。经过一些试验,这似乎是因为我正在通过 async(UI) 使用 UI 上下文。如果我将其设为常规异步,我可以让测试通过,但随后应用程序崩溃,因为我在 UI 线程之外修改 View 。

知道为什么会发生这种情况吗?此外,如果有人对此有任何更好的建议,这将使它更易于测试,我会洗耳恭听。

谢谢。

编辑: 忘记提及我还尝试将 verifyuut?.onRefresh() 包装在 runBlocking 中,但我仍然没有成功。

最佳答案

如果你想让事情变得干净并考虑在未来使用 MVP 架构,你应该明白 CourutineContext 是外部依赖,应该通过 DI 注入(inject),或传递给你的演示者。 More details on topic .

您的问题的答案很简单,您应该只使用 Unconfined CourutineContext 进行测试。 ( more ) 为了简单起见,创建一个对象,例如注入(inject):

package com.example

object Injection {
    val uiContext : CourutineContext = UI
    val bgContext : CourutineContext = CommonPool
}

并在测试包中创建完全相同的对象,但更改为:

package com.example

object Injection {
    val uiContext : CourutineContext = Unconfined
    val bgContext : CourutineContext = Unconfined
}

在你的类里面它会是这样的:

val data = async(Injection.bgContext) {service?.getData()}.await()

关于android - UI 线程上的单元测试协程,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47781377/

相关文章:

android - 为什么 Spinner.getChildAt 返回 null?

android - 重复类 kotlin 类 kotlin 版本 1.3.70

java - 私有(private)类变量与过多的参数传递

java - 从 Spring boot 单元测试中排除 Spring Cloud Config Server

Kotlin,针对 Java 互操作 : Idiomatic type for lazy collection?

android - 我如何访问 Kotlin 中的枚举值

android - java.lang.SecurityException : Permission Denial: not allowed to send broadcast android. intent.action.MEDIA_MOUNTED 仅在 KitKat 上

unit-testing - Angular2(TypeScript)中的单元测试/模拟窗口属性

ruby-on-rails - 打印 Rails 测试名称以查找最慢的测试

java - 获取高阶函数中函数参数的名称