David Michael Gang
David Michael Gang

Reputation: 7309

How to use TestCoroutineScope with android datastore?

I have the following code:

class Feature(scope:CoroutineScope = CoroutineScope(Dispatchers.IO)){
 val dataStore: DataStore<MetaDataStore> = context.createDataStore(
        fileName = PREFERENCES,
        serializer = MetadataSerializer,
        scope = scope
    )
fun foo() {
}
}

When testing the class i want to check the function foo

runBlockingTest {
            val feature = Feature(this)
             feature.foo()
             verifyStuff...

        }

It is important to use the TestCoroutineScope as it ensures me that any async stuff was finished Unfortunately i get an error message:

kotlinx.coroutines.test.UncompletedCoroutinesError: Test finished with active jobs: ["coroutine#2":ActorCoroutine{Active}@31b46ea7]

It makes sense as there may be background tasks used by datastore, but then how do i test classes using android datastore?

I also asked the question in google issue tracker: https://issuetracker.google.com/issues/177856517

In the meantime i backed some mock implementation and inject it in the constructor.

import androidx.datastore.core.DataStore
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock

class DatastoreInmemoryImpl<T>(defaultValue:T) : DataStore<T> {
    val sharedFlow = MutableStateFlow(defaultValue)
    val mutex = Mutex()
    override val data: Flow<T> = sharedFlow.asStateFlow()
    override suspend fun updateData(transform: suspend (t: T) -> T): T = mutex.withLock {
        sharedFlow.value = transform.invoke(sharedFlow.value)
        sharedFlow.value
    }
}

But i should be able to use a class using datastore without needing to inject it as a constructor parameter

Upvotes: 1

Views: 2384

Answers (2)

Stylianos Gakis
Stylianos Gakis

Reputation: 1023

With the release notes of Version 1.1.0-rc01 the suggestion seems to be to use TestScope.backgroundScope to pass into PreferenceDataStoreFactory.create([here]... for your tests running on runTest

Upvotes: 1

David Michael Gang
David Michael Gang

Reputation: 7309

The google guys answered me.

There is a test example here

The main idea is to create a different scope from the enclosing test scope and cancel it afterwards

so

runBlockingTest {
  val dataStoreScope = TestCoroutineScope(TestCoroutineDispatcher() + Job())

  val feature = Feature(dataStoreScope)
  <put your test code here>
  dataStoreScope.cancel()
  dataStoreScope.cleanupTestCoroutines()
}

The datastoreScope creation and datastoreScope.cleanupTestCoroutines can be put in a @Before and @After function.

They told me that they are working on a better solution for a later release

Upvotes: 1

Related Questions