【问题标题】:Testing Function with Multiple Coroutine Delays具有多个协程延迟的测试函数
【发布时间】:2022-01-10 12:05:03
【问题描述】:

有人可以建议如何在函数中协程延迟背后的代码行上断言。我无法使用注入的dispatchers 和使用runBlockingTest 来做到这一点。我还更新了我的项目依赖项并尝试使用较新的 runTest 无济于事。

请有人建议。

代码示例:

val liveData1 = MutableLiveData(false)

fun foo() {
    doTheThing(liveData1, {lambda1(liveData1)})
}

fun doTheThing(liveData1: LiveData<Boolean>, f1: () -> Unit) {
    if (!liveData1.value) {
        f1()
    } 
}

fun lambda1(liveData1: LiveData<Boolean>) {
    viewModelScope.launch(dispatchers.main) {
        delay(1000)
        liveData1.postValue(true)
        delay(1000)
        liveData1.postValue(false)
    }
}

测试示例:

@ExperimentalCoroutinesApi
@Test `test doTheThing`() = runBlockingTest{
    val subject = MyClass(TestCoroutineDispatchers())
    
    val observer1 = subject.liveData1.test()

    observers1.assertValueHistory(false)
    
    subject.foo()

    observers1.assertValueHistory(false, true, false) // fails here stating should have history [false]!=[false, true, false]
}

我已经检查过了,如果我将延迟设置为 0,那么我的断言是正确的。我已经通过调试器,测试总是将代码运行到第一个延迟,但永远不会在延迟之后到达代码。

LiveData 测试辅助函数:

fun <T> LiveData<T>.test(): TestObserver<T> = TestObserver.test(this)

********

public TestObserver<T> assertValueHistory(T... values) {
    List<T> mValueHistory = valueHistory();
    int size = mValueHistory.size();
    if (size != values.length) {
        throw fail("Value count differs; expected: " + values.length + " " + Arrays.toString(values)
                + " but was: " + size + " " + this.valueHistory);
    }

    for (int valueIndex = 0; valueIndex < size; valueIndex++) {
        T historyItem = mValueHistory.get(valueIndex);
        T expectedItem = values[valueIndex];
        if (notEquals(expectedItem, historyItem)) {
            throw fail("Values at position " + valueIndex + " differ; expected: " + valueAndClass(expectedItem) + " but was: " + valueAndClass(historyItem));
        }
    }

    return this;
}

【问题讨论】:

    标签: android kotlin delay kotlin-coroutines


    【解决方案1】:

    我最终做的是将 Dispatcher 包装在一个接口中。

    interface IDispatcherProvider{
       val main : Dispatcher
       val io : Dispatcher
       val default: Dispatcher
       val unconfined: Dispatcher
    }
    

    这允许您的代码部署具有:

    object DispatcherProvider : IDispatcherProvider{
       val main : Dispatcher = Dispatchers.Main
       val io : Dispatcher = Dispatchers.IO
       val default: Dispatcher = Dispatchers.Default
       val unconfined: Dispatcher = Dispatchers.Unconfined
    }
    

    然后你传递接口,以便你可以注入 TestProvider:

    object TestDispatcherProvider : IDispatcherProvider{
       val main : Dispatcher = TestCoroutineDispatcher()
       val io : Dispatcher = TestCoroutineDispatcher()
       val default: Dispatcher = TestCoroutineDispatcher()
       val unconfined: Dispatcher = TestCoroutineDispatcher()
    }
    

    这样您的测试将不使用线程池,而是按顺序运行。如果您重新分配调度程序,还有另一种遵循相同前提的方法,但我不记得它是什么,这与我们所做的非常接近(使用 DI 传递)并且在我们的协程测试中运行良好. 所以在你的课堂上:

    class SomeClass(private val dispatcher: IDispatcherProvider = DispatcherProvider){
    val liveData1 = MutableLiveData(false)
    
    fun foo() {
        doTheThing(liveData1, {lambda1(liveData1)})
    }
    
    fun doTheThing(liveData1: LiveData<Boolean>, f1: () -> Unit) {
        if (!liveData1.value) {
            f1()
        } 
    }
    
    fun lambda1(liveData1: LiveData<Boolean>) {
        viewModelScope.launch(dispatcher.main) {
            delay(1000)
            liveData1.postValue(true)
            delay(1000)
            liveData1.postValue(false)
        }
    }
    }
    

    【讨论】:

    • 对不起,我不清楚。当我在我的问题中说我无法使用注入的调度程序和 runBlockingTest 执行此操作时,我的意思是我已经尝试使用您描述的方法注入 TestCoroutineDispatcher()。我找到了一个解决方案 - 发布在下面。
    【解决方案2】:

    我想通了。您需要使用以下调度程序。仅 TestCoroutineDispatcher() 是不够的……

    @ExperimentalCoroutinesApi
    @InternalCoroutinesApi
    object SynchronousDispatchersWithNoDelay : Dispatchers {
    
        override val io: CoroutineDispatcher
            get() = NoDelayDispatcher()
    
        override val main: CoroutineDispatcher
            get() = NoDelayDispatcher()
    
        class NoDelayDispatcher : CoroutineDispatcher(), Delay {
    
            override fun scheduleResumeAfterDelay(timeMillis: Long, continuation: CancellableContinuation<Unit>) { 
                continuation.resume(Unit) {} 
            }
    
            override fun dispatch(context: CoroutineContext, block: Runnable) {
                block.run()
            }
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-04-20
      • 2019-04-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多