使用 androidx.datastore 版本 1.1.1 的仪器测试现在失败并出现 UncompletedCoroutinesError

问题描述 投票:0回答:1

我将 app/build.gradle 中的这些实现从 1.0.0 更新到 1.1.1:

implementation "androidx.datastore:datastore-preferences:$androidXDatastore"
implementation "androidx.datastore:datastore-core:$androidXDatastore"
implementation "androidx.datastore:datastore-preferences-core:$androidXDatastore"

此后,一些仪器测试失败了,尽管生产代码仍然可以正常工作:

kotlinx.coroutines.test.UncompletedCoroutinesError: After waiting for 10s, the test coroutine is not completing, there were active child jobs: ["coroutine#1":StandaloneCoroutine{Active}@284ce4db]
    at kotlinx.coroutines.test.TestBuildersKt__TestBuildersKt$runTest$2$1$2$1.invoke(TestBuilders.kt:349)
    at kotlinx.coroutines.test.TestBuildersKt__TestBuildersKt$runTest$2$1$2$1.invoke(TestBuilders.kt:333)
    at kotlinx.coroutines.InvokeOnCancelling.invoke(JobSupport.kt:1431)
    at kotlinx.coroutines.JobSupport.notifyCancelling(JobSupport.kt:1477)
    at kotlinx.coroutines.JobSupport.tryMakeCancelling(JobSupport.kt:799)
    at kotlinx.coroutines.JobSupport.makeCancelling(JobSupport.kt:759)
    at kotlinx.coroutines.JobSupport.cancelImpl$kotlinx_coroutines_core(JobSupport.kt:675)
    at kotlinx.coroutines.JobSupport.cancelCoroutine(JobSupport.kt:662)
    at kotlinx.coroutines.TimeoutCoroutine.run(Timeout.kt:159)
    at kotlinx.coroutines.EventLoopImplBase$DelayedRunnableTask.run(EventLoop.common.kt:501)
    at kotlinx.coroutines.EventLoopImplBase.processNextEvent(EventLoop.common.kt:280)
    at kotlinx.coroutines.DefaultExecutor.run(DefaultExecutor.kt:109)
    at [email protected]/java.lang.Thread.run(Thread.java:833)

目标班级

class ScrollPositionDataStore(private val dataStore: DataStore<Preferences>) {
    private val _displayedPositionOffsetKey = stringPreferencesKey("displayed_position_offset")

    suspend fun saveScrollPositionStore(topRowIndex: Int, positionOffset: Int) {
        val position = "$topRowIndex,$positionOffset"
        dataStore.edit { preferences ->
            preferences[_displayedPositionOffsetKey] = position
        }
    }

    suspend fun getScrollPosition(): Pair<Int, Int>? = scrollPositionFlow.firstOrNull()

    private val scrollPositionFlow: Flow<Pair<Int, Int>> = dataStore.data
        .map { preferences ->
            // On the first run of the app, we will use LinearLayoutManager by default
            val i = preferences[_displayedPositionOffsetKey]
            val split = i?.split(",")
            if (split == null) Pair(4, 0)
            else Pair(split[0].toInt(), split[1].toInt())
        }
}

测试课


private const val POSITION_PREFERENCES_NAME = "position_preferences"

@ExperimentalCoroutinesApi
@RunWith(AndroidJUnit4::class)
class ScrollPositionDataStoreTest : JunitDataStoreTest(POSITION_PREFERENCES_NAME) {
    private var inputDataStore: ScrollPositionDataStore = ScrollPositionDataStore(dataStore)

    @Test
    fun `ScrollPositionDataStore 入力した値をそのまま帰す`() {
        scope.runTest {
            dataStore.edit { it.clear() }
            inputDataStore.saveScrollPositionStore(10, 15)
            assertThat(inputDataStore.getScrollPosition())
                .isEqualTo(Pair(10, 15))
        }
    }

    @Test
    fun `ScrollPositionDataStore 未入力時に400を返す`() {
        scope.runTest {
            dataStore.edit { it.clear() }
            assertThat(inputDataStore.getScrollPosition())
                .isEqualTo(Pair(4, 0))
        }
    }

    @Test
    fun `ScrollPositionDataStore マイナス値をそのまま還す`() {
        scope.runTest {
            dataStore.edit { it.clear() }
            inputDataStore.saveScrollPositionStore(-1, -111)
            assertThat(inputDataStore.getScrollPosition())
                .isEqualTo(Pair(-1, -111))
        }
    }
}


@ExperimentalCoroutinesApi
abstract class JunitDataStoreTest(dataStoreName :String) {

    private val dispatcher = UnconfinedTestDispatcher()
    private val context: Context = InstrumentationRegistry.getInstrumentation().targetContext
    val scope = TestScope(dispatcher + Job())
    val dataStore = PreferenceDataStoreFactory.create(
        scope = scope,
        produceFile = { context.preferencesDataStoreFile(dataStoreName) }
    )

    @CallSuper
    @Before
    open fun setUp() {
        Dispatchers.setMain(dispatcher)
    }

    @CallSuper
    @After
    open fun tearDown() {
        Dispatchers.resetMain()
    }
}

恢复到1.0.0版本后测试工作正常,所以问题肯定是由新的数据存储版本引起的。

如何使用新的数据存储版本让我的测试再次通过?

android kotlin android-instrumentation android-jetpack-datastore
1个回答
0
投票

这是由 1.1.0 版本引入的 性能改进引起的。

推荐的解决方案(请参阅上面的链接)是为数据存储初始化提供

backgroundScope
。它不会等待协程在测试结束时完成:

val dataStore = PreferenceDataStoreFactory.create(
    scope = scope.backgroundScope,
    produceFile = { context.preferencesDataStoreFile(dataStoreName) }
)
© www.soinside.com 2019 - 2024. All rights reserved.