android / android/codelab-android-datastore

How to test Proto datastore? The Medium article by Simona Stojanovic doesn't work for proto datastore

Open
#61 0 comments 0 reactions 1 assignee Claimed by @simona-anomis View on GitHub
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

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.