Reputation: 7309
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
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
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