gpt4 book ai didi

Android Kotlin 协程单元测试

转载 作者:行者123 更新时间:2023-11-29 14:58:12 25 4
gpt4 key购买 nike

我有一个启动协程的 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)
}
}

下面是测试:

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

发生的事情是我正在验证的函数从未被调用过。测试在协程返回之前结束...我知道 GlobalScope 不好用,但我不确定该怎么做。

编辑 1:如果我在 verify 之前延迟,它似乎可以工作,因为它允许协程有时间完成并返回,但是,我不想依赖延迟/ sleep 进行测试。 . 我认为解决方案是适本地引入一个范围而不是使用 GlobalScope 并在测试中控制它。 las,我不知道声明协程作用域的约定是什么。

最佳答案

我明白了,你必须使用 Unconfined调度员:

val Unconfined: CoroutineDispatcher (source)

A coroutine dispatcher that is not confined to any specific thread. It executes the initial continuation of a coroutine in the current call-frame and lets the coroutine resume in whatever thread that is used by the corresponding suspending function, without mandating any specific threading policy. Nested coroutines launched in this dispatcher form an event-loop to avoid stack overflows.

Documentation sample:

withContext(Dispatcher.Unconfined) {
println(1)
withContext(Dispatcher.Unconfined) { // Nested unconfined
println(2)
}
println(3)
}
println("Done")

对于我的 ViewModel 测试,我将协程上下文传递给 ViewModel 构造函数,以便我可以在 Unconfined 和其他调度程序之间切换,例如Dispatchers.MainDispatchers.IO

测试的协程上下文:

@ExperimentalCoroutinesApi
class TestContextProvider : CoroutineContextProvider() {
override val Main: CoroutineContext = Unconfined
override val IO: CoroutineContext = Unconfined
}

实际 ViewModel 实现的协程上下文:

open class CoroutineContextProvider {
open val Main: CoroutineContext by lazy { Dispatchers.Main }
open val IO: CoroutineContext by lazy { Dispatchers.IO }
}

View 模型:

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

}

更新

从协程核心版本 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"

例如:

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

关于Android Kotlin 协程单元测试,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56531647/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com