android - 如何运行协程作为单元测试的阻塞?
问题描述
我已经开始为我的 MVP Android 项目编写单元测试,但是我依赖于协程的测试间歇性地失败(通过日志记录和调试,我确认验证有时会提前发生,delay
当然会添加修复)
我已经尝试过包装runBlocking
并且我发现了Dispatchers.setMain(mainThreadSurrogate)
from org.jetbrains.kotlinx:kotlinx-coroutines-test
,但是尝试了这么多组合到目前为止还没有取得任何成功。
abstract class CoroutinePresenter : Presenter, CoroutineScope {
private lateinit var job: Job
override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Main
override fun onCreate() {
super.onCreate()
job = Job()
}
override fun onDestroy() {
super.onDestroy()
job.cancel()
}
}
class MainPresenter @Inject constructor(private val getInfoUsecase: GetInfoUsecase) : CoroutinePresenter() {
lateinit var view: View
fun inject(view: View) {
this.view = view
}
override fun onResume() {
super.onResume()
refreshInfo()
}
fun refreshInfo() = launch {
view.showLoading()
view.showInfo(getInfoUsecase.getInfo())
view.hideLoading()
}
interface View {
fun showLoading()
fun hideLoading()
fun showInfo(info: Info)
}
}
class MainPresenterTest {
private val mainThreadSurrogate = newSingleThreadContext("Mocked UI thread")
private lateinit var presenter: MainPresenter
private lateinit var view: MainPresenter.View
val expectedInfo = Info()
@Before
fun setUp() {
Dispatchers.setMain(mainThreadSurrogate)
view = mock()
val mockInfoUseCase = mock<GetInfoUsecase> {
on { runBlocking { getInfo() } } doReturn expectedInfo
}
presenter = MainPresenter(mockInfoUseCase)
presenter.inject(view)
presenter.onCreate()
}
@Test
fun onResume_RefreshView() {
presenter.onResume()
verify(view).showLoading()
verify(view).showInfo(expectedInfo)
verify(view).hideLoading()
}
@After
fun tearDown() {
Dispatchers.resetMain()
mainThreadSurrogate.close()
}
}
我相信这些runBlocking
块应该迫使所有孩子coroutineScopes
在同一个线程上运行,迫使他们在继续验证之前完成。
解决方案
在CoroutinePresenter
课堂上你正在使用Dispatchers.Main
. 您应该能够在测试中更改它。尝试执行以下操作:
uiContext: CoroutineContext
向演示者的构造函数添加参数:abstract class CoroutinePresenter(private val uiContext: CoroutineContext = Dispatchers.Main) : CoroutineScope { private lateinit var job: Job override val coroutineContext: CoroutineContext get() = uiContext + job //... } class MainPresenter(private val getInfoUsecase: GetInfoUsecase, private val uiContext: CoroutineContext = Dispatchers.Main ) : CoroutinePresenter(uiContext) { ... }
更改
MainPresenterTest
类以注入另一个CoroutineContext
:class MainPresenterTest { private lateinit var presenter: MainPresenter @Mock private lateinit var view: MainPresenter.View @Mock private lateinit var mockInfoUseCase: GetInfoUsecase val expectedInfo = Info() @Before fun setUp() { // Mockito has a very convenient way to inject mocks by using the @Mock annotation. To // inject the mocks in the test the initMocks method needs to be called. MockitoAnnotations.initMocks(this) presenter = MainPresenter(mockInfoUseCase, Dispatchers.Unconfined) // here another CoroutineContext is injected presenter.inject(view) presenter.onCreate() } @Test fun onResume_RefreshView() = runBlocking { Mockito.`when`(mockInfoUseCase.getInfo()).thenReturn(expectedInfo) presenter.onResume() verify(view).showLoading() verify(view).showInfo(expectedInfo) verify(view).hideLoading() } }
推荐阅读
- android - Android apk 部署失败 (INSTALL_FAILED_UPDATE_INCOMPATIBLE)
- django - 为 forms.Form __init__ 添加额外的初始化参数
- angular - Angular 8 中的日期验证器来比较两个日期
- python - 计数反转算法中的错误
- javascript - Javascript 不工作,但浏览器中的相同代码可以工作
- mysql - Docker:未创建数据库
- android - 如何将 XML 中的字符串数组和整数数组添加到一个集合中
- python - 使用 rpy2 库在 R 中的 for 循环比在 Python 中快几倍
- java - 使用带有阻塞/同步请求的 Spring 的 WebClient 使用 try/catch 捕获异常
- python - Pandas - 删除行后标题不会改变