UI线程上的单元测试协同程序

时间:2017-12-12 20:54:57

标签: android unit-testing kotlin kotlinx.coroutines

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

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
    }
}

当我将它放在设备上时,这里的一切都运行正常。我的错误,空白和数据状态都处理得很好,性能也很好。但是,我也试图用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线程之外的视图。

为什么会出现这种情况的任何想法?此外,如果有人有任何更好的建议,这将使它更容易测试,我听见了。

感谢。

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

1 个答案:

答案 0 :(得分:5)

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

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

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()