Mit*_*tch 1 android kotlin kotlin-coroutines
我有一个开始启动协程的broadcastReceiver,我正在尝试对该单元进行测试...
广播:
class AlarmBroadcastReceiver: BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
Timber.d("Starting alarm from broadcast receiver")
//inject(context) Don't worry about this, it's mocked out
GlobalScope.launch {
val alarm = getAlarm(intent)
startTriggerActivity(alarm, context)
}
}
private suspend fun getAlarm(intent: Intent?): Alarm {
val alarmId = intent?.getIntExtra(AndroidAlarmService.ALARM_ID_KEY, -1)
if (alarmId == null || alarmId < 0) {
throw RuntimeException("Cannot start an alarm with an invalid ID.")
}
return withContext(Dispatchers.IO) {
alarmRepository.getAlarmById(alarmId)
}
}
Run Code Online (Sandbox Code Playgroud)
这是测试:
@Test
fun onReceive_ValidAlarm_StartsTriggerActivity() {
val alarm = Alarm().apply { id = 100 }
val intent: Intent = mock {
on { getIntExtra(any(), any()) }.thenReturn(alarm.id)
}
whenever(alarmRepository.getAlarmById(alarm.id)).thenReturn(alarm)
alarmBroadcastReceiver.onReceive(context, intent)
verify(context).startActivity(any())
}
Run Code Online (Sandbox Code Playgroud)
发生的事情是我正在验证的函数从未调用过。测试在协程返回之前结束...我知道这GlobalScope不好用,但是我不确定该怎么做。
编辑1:如果我在之前放置了一个延迟verify,它似乎可以正常工作,因为它允许协程完成并返回的时间,但是,我不想依赖延迟/睡眠进行测试...我认为解决方案是正确引入一个范围,而不是GlobalScope在测试中使用和控制该范围。las,我不知道声明协程范围的约定是什么。
我知道,您将必须使用Unconfined调度程序:
val Unconfined: CoroutineDispatcher (source)不局限于任何特定线程的协程调度程序。它在当前调用框架中执行协程的初始延续,并让协程在相应的挂起函数使用的任何线程中恢复,而无需强制执行任何特定的线程策略。在此调度程序中启动的嵌套协程形成一个事件循环,以避免堆栈溢出。
文档样本:
Run Code Online (Sandbox Code Playgroud)withContext(Dispatcher.Unconfined) { println(1) withContext(Dispatcher.Unconfined) { // Nested unconfined println(2) } println(3) } println("Done")
对于我的ViewModel测试,我将协程上下文传递给ViewModel构造函数,以便可以在Unconfined和其他调度程序之间切换,例如Dispatchers.Main和Dispatchers.IO。
测试的协程上下文:
@ExperimentalCoroutinesApi
class TestContextProvider : CoroutineContextProvider() {
override val Main: CoroutineContext = Unconfined
override val IO: CoroutineContext = Unconfined
}
Run Code Online (Sandbox Code Playgroud)
实际ViewModel实现的协程上下文:
open class CoroutineContextProvider {
open val Main: CoroutineContext by lazy { Dispatchers.Main }
open val IO: CoroutineContext by lazy { Dispatchers.IO }
}
Run Code Online (Sandbox Code Playgroud)
ViewModel:
@OpenForTesting
class SampleViewModel @Inject constructor(
val coroutineContextProvider: CoroutineContextProvider
) : ViewModel(), CoroutineScope {
private val job = Job()
override val coroutineContext: CoroutineContext = job + coroutineContextProvider.Main
override fun onCleared() = job.cancel()
fun fetchData() {
launch {
val response = withContext(coroutineContextProvider.IO) {
repository.fetchData()
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
从协程核心版本开始,1.2.1您可以使用runBlockingTest:
依存关系:
def coroutines_version = "1.2.1"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version"
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutines_version"
Run Code Online (Sandbox Code Playgroud)
例如:
@Test
fun `sendViewState() sends displayError`(): Unit = runBlockingTest {
Dispatchers.setMain(Dispatchers.Unconfined)
val apiResponse = ApiResponse.success(data)
whenever(repository.fetchData()).thenReturn(apiResponse)
viewModel.viewState.observeForever(observer)
viewModel.processData()
verify(observer).onChanged(expectedViewStateSubmitError)
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
258 次 |
| 最近记录: |