android / android/codelab-android-datastore
How to test Proto datastore? The Medium article by Simona Stojanovic doesn't work for proto datastore
- Dominant language
- Kotlin
- Stars
- 240
- Forks
- 113
- PR merge metrics
- No merged PRs in 30d
Description
I tried the approach in Simona Stojanovic @simona-anomis [Medium article](https://medium.com/androiddevelopers/datastore-and-testing-edf7ae8df3d8) but it doesn't work for proto datastore. The issue is that `testCoroutineScope.runBlockingTest` gives me an error.
The entire code is the same as in the Medium article except for the part where the datastore is created:
```
DataStoreFactory.create(
serializer = UserPreferencesSerializer,
produceFile = { testContext.dataStoreFile(DATA_STORE_FILE_NAME) },
scope = testCoroutineScope
)
```
This is the full test file I run as an Instrumented Test:
```
package com.xkcd.comiclist_data.repository
import android.content.Context
import androidx.datastore.core.DataStore
import androidx.datastore.core.DataStoreFactory
import androidx.datastore.dataStoreFile
import androidx.test.platform.app.InstrumentationRegistry
import app.cash.turbine.test
import com.xkcd.core.XkcdSettings
import com.xkcd.core.settings.XkcdSettingsSerializer
import dagger.hilt.android.testing.HiltAndroidRule
import dagger.hilt.android.testing.HiltAndroidTest
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.test.*
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
@HiltAndroidTest
class XkcdSettingsRepositoryImplTest {
@get:Rule
var hiltRule = HiltAndroidRule(this)
private val testContext: Context =
InstrumentationRegistry.getInstrumentation().targetContext
private val testCoroutineDispatcher: TestCoroutineDispatcher =
TestCoroutineDispatcher()
@OptIn(ExperimentalCoroutinesApi::class)
private val testCoroutineScope =
TestCoroutineScope(testCoroutineDispatcher + Job())
private val testDataStore: DataStore = DataStoreFactory.create(
serializer = XkcdSettingsSerializer,
produceFile = { testContext.dataStoreFile(XkcdSettingsSerializer.testDataStoreName) },
scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
)
private val repository: XkcdSettingsRepositoryImpl = XkcdSettingsRepositoryImpl(testDataStore)
@OptIn(ExperimentalCoroutinesApi::class)
@Before
fun setup() {
Dispatchers.setMain(testCoroutineDispatcher)
}
@OptIn(ExperimentalCoroutinesApi::class)
@After
fun cleanUp() {
Dispatchers.resetMain()
testCoroutineDispatcher.cleanupTestCoroutines()
testCoroutineScope.cancel()
}
@OptIn(ExperimentalCoroutinesApi::class)
@Test
fun dummyTest() {
testCoroutineScope.runBlockingTest {
repository.getXkcdSettings().test {
println(awaitItem())
awaitComplete()
}
}
}
}
```
The error I get states that `This job has not completed yet`:
```
java.lang.IllegalStateException: This job has not completed yet
at kotlinx.coroutines.JobSupport.getCompletionExceptionOrNull(JobSupport.kt:1190)
at kotlinx.coroutines.test.TestBuildersKt__TestBuildersDeprecatedKt.runBlockingTest(TestBuildersDeprecated.kt:67)
at kotlinx.coroutines.test.TestBuildersKt.runBlockingTest(Unknown Source:1)
at kotlinx.coroutines.test.TestBuildersKt__TestBuildersDeprecatedKt.runBlockingTest(TestBuildersDeprecated.kt:126)
at kotlinx.coroutines.test.TestBuildersKt.runBlockingTest(Unknown Source:1)
at com.xkcd.comiclist_data.repository.XkcdSettingsRepositoryImplTest.dummyTest(XkcdSettingsRepositoryImplTest.kt:60)
```
Here is the full `XkcdSettingsRepositoryImpl`:
```
package com.xkcd.comiclist_data.repository
import androidx.datastore.core.DataStore
import com.xkcd.comiclist_domain.model.Comic
import com.xkcd.comiclist_domain.model.CurrentXkcdSettings
import com.xkcd.comiclist_domain.repository.XkcdSettingsRepository
import com.xkcd.core.XkcdSettings
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.flow
import javax.inject.Inject
import kotlin.math.max
class XkcdSettingsRepositoryImpl @Inject constructor(
private val xkcdSettings: DataStore
): XkcdSettingsRepository {
override suspend fun updateUserName(userName: String) {
xkcdSettings.updateData { settings ->
settings.toBuilder().setUserName(userName).build()
}
}
override suspend fun updateLatestComic(comic: Comic) {
xkcdSettings.updateData { settings ->
settings.toBuilder().setLatestComicNum(max(comic.num, settings.latestComicNum.toInt()).toLong()).build()
}
}
override suspend fun updateSelectedTab(selectedTab: XkcdSettings.SelectedTab) {
xkcdSettings.updateData { settings ->
settings.toBuilder().setSelectedComicTab(selectedTab).build()
}
}
override suspend fun updateSelectedComicSort(selectedComicSort: XkcdSettings.SelectedComicSort) {
xkcdSettings.updateData { settings ->
settings.toBuilder().setSelectedComicSort(selectedComicSort).build()
}
}
override suspend fun getXkcdSettings(): Flow = flow {
xkcdSettings.data.collectLatest {
emit(CurrentXkcdSettings(
userName = it.userName,
latestComicNum = it.latestComicNum.toInt(),
selectedTab = it.selectedComicTab,
selectedComicSort = it.selectedComicSort
))
}
}
}
```
Contributor guide
Assessment
This issue has not been assessed yet.