UI 线程上的单元测试协程
Unit testing coroutines on UI thread
我正在使用协同程序对拉动进行异步调用以刷新,如下所示:
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 线程之外修改视图。
知道为什么会发生这种情况吗?另外,如果有人对此有更好的建议,这将使它更易于测试,我会洗耳恭听。
谢谢。
编辑: 忘了说我也试过将 verify
和 uut?.onRefresh()
包装在 runBlocking
中,但我仍然有没有成功。
如果你想让事情变得干净并考虑在未来使用 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
}
在你的 class 里面会是这样的:
val data = async(Injection.bgContext) {service?.getData()}.await()
我正在使用协同程序对拉动进行异步调用以刷新,如下所示:
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 线程之外修改视图。
知道为什么会发生这种情况吗?另外,如果有人对此有更好的建议,这将使它更易于测试,我会洗耳恭听。
谢谢。
编辑: 忘了说我也试过将 verify
和 uut?.onRefresh()
包装在 runBlocking
中,但我仍然有没有成功。
如果你想让事情变得干净并考虑在未来使用 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
}
在你的 class 里面会是这样的:
val data = async(Injection.bgContext) {service?.getData()}.await()