The way to Check Proto DataStore

In a previous article, I described how you should utilize Proto DataStore in your software. I wrote that article as a part of my expertise in utilizing Proto DataStore in one of my applications.
Following that, I needed to see what it could be like to write down exams for the Proto DataStore in that software utilizing the data I gained.
Looking out on-line for steering didn’t present a lot aid, so I figured I’d share my data for people who could be searching for it. Within the worst case, it could be for my progeny.
In my search, I did discover this article, however that focuses totally on testing the Preferences DataStore and never the Proto DataStore. It does state that:
“Nonetheless, take note you should utilize this materials for establishing Proto DataStore testing, as it could be similar to Preferences.”
However having adopted it, I discovered that in addition to the dependencies, there aren’t many similarities right here and you want to introduce separate logic to check your individual Proto DataStore.
Setting Issues Up
In your software’s construct.gradle file, add the next dependencies:
dependencies {
///.....
androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose_version"
debugImplementation "androidx.compose.ui:ui-test-manifest:$compose_version"
}
$compose_version
is the variable you outlined in your venture stage construct.gradle file.
Then, go to your androidTest
listing and create a brand new file. Normally, you’d have a repository class that interacts along with your Proto DataStore, so you may identify this file as YourRepositoryClassNameTest. We’ll use the identify MyRepositoryTest
.
Earlier than we delve into testing the Proto DataStore itself, we have to instantiate it. If you happen to log on to seek out any documentation on this, it’s form of sparse.
Instantiating a Proto DataStore is used with the worldwide Context like so (when not utilized in a testing state of affairs):
non-public val Context.myDataStore: DataStore<MyItem> by dataStore(
fileName = DATA_STORE_FILE_NAME,
serializer = MyItemSerializer
)
Properly, you may’t do that within a check class, as a result of, whilst you can copy-paste the above code, you received’t be capable to entry the DataStore object. You may get the applying context like this:
ApplicationProvider.getApplicationContext()
however our myDataStore
object received’t be accessible by way of it.
So, what can we do?
Within the article linked above, there may be an instance of how we are able to create a Choice DataStore utilizing the PreferenceDataStoreFactory.create methodology.
enjoyable create(
corruptionHandler: ReplaceFileCorruptionHandler<Preferences>? = null,
migrations: Record<DataMigration<Preferences>> = listOf(),
scope: CoroutineScope = CoroutineScope(Dispatchers.IO + SupervisorJob()),
produceFile: () -> File): DataStore<Preferences>
However since we aren’t utilizing a Choice DataStore, that received’t work for us. What is going to work is utilizing the DataStoreFactory.create methodology like this:
enjoyable <T : Any?> create(
serializer: Serializer<T>,
corruptionHandler: ReplaceFileCorruptionHandler<T>? = null,
migrations: Record<DataMigration<T>> = listOf(),
scope: CoroutineScope = CoroutineScope(Dispatchers.IO + SupervisorJob()), produceFile: () -> File): DataStore<T>
There are a number of arguments for this methodology (and a few have default values), however we don’t have to go all of them in. We can be passing:
- Our serializer class
- A lambda methodology for creating the file for our Proto DataStore
dataStore = DataStoreFactory.create(
produceFile = {
testContext.dataStoreFile(TEST_DATA_STORE_FILE_NAME) },
serializer = MyItemSerializer
)
We get the testContext
by:
non-public val testContext: Context = ApplicationProvider.getApplicationContext()
The way to Check the DataStore
Having created our Proto DataStore efficiently, we are able to transfer on to writing some exams for it. Remember that you might have a repository class that receives the occasion of the Proto DataStore as a dependency, so after creating the Proto DataStore, we have to create an occasion of our repository class.
non-public val repository = MyRepository(datastore)
First, let’s create a check to verify our preliminary Proto DataStore state. The Proto DataStore itself exposes a stream which we are able to use.
@OptIn(ExperimentalCoroutinesApi::class)
@Check
enjoyable repository_testFetchInitialState() {
runTest {
testScope.launch {
val dataStoreObject = repository.myFlow.first()
assert(dataStoreObject.myFlag == false)
}
}
}
You might have seen this earlier, however we’re utilizing a OptIn annotation right here. It is because (at the moment) the APIs which we’re utilizing are experimental and have to be marked so once we use them.
Since we’re accessing the stream of our DataStore
, we have to wrap it in our testScope
. TestScope
is created by doing:
@OptIn(ExperimentalCoroutinesApi::class)
non-public val dispatcher = TestCoroutineDispatcher()
@OptIn(ExperimentalCoroutinesApi::class)
non-public val testScope = TestCoroutineScope(dispatcher)
Run it and revel in your first Proto DataStore check.
That was enjoyable for about two seconds.
Let’s do one thing extra significant.
Think about our Proto DataStore has a listing of objects within it and we wish to check the state of it once we add an merchandise to it.
@OptIn(ExperimentalCoroutinesApi::class)
@Check
enjoyable repository_testAdditionOfItem() {
runTest {
testScope.launch {
val merchandise: MyItem = MyItem.newBuilder().setItemId(UUID.randomUUID().toString())
.setItemDescription(TEST_ITEM_DESCRIPTION).construct()
repository.updateItem(merchandise)
val objects = repository.myFlow.first().itemsList
assert(objects.dimension == 1)
assert(objects[0].itemDescription.equals(TEST_ITEM_DESCRIPTION))
}
}
}
- We create a check merchandise utilizing the uncovered API from the protobuff
- We add this merchandise to the Proto DataStore utilizing a technique we uncovered on the MyRepository class
- We seize the listing of things from the stream the Proto DataStore exposes
- We make it possible for the merchandise discovered within the Proto DataStore matches the merchandise we created earlier
Your DataStore Has a Leak in it
If you happen to attempt to run the exams above in a single go, you’ll quickly obtain an error throughout runtime:
There are a number of DataStores energetic for a similar file: /knowledge/consumer/0/com.instance.app/information/datastore/dataStore_filename.pb. It is best to both keep your DataStore as a singleton or affirm that there isn’t any two DataStore’s energetic on the identical file (by confirming that the scope is cancelled).
Properly, that’s problematic. We solely created one DataStore occasion in our check class.
What’s going on right here?
As a result of we aren’t utilizing the property delegate to create our DataStore (which means Context.datastore), it isn’t ensured that our DataStore object is a singleton each time we entry it.
To avoid this state of affairs, I’ve discovered that one method is to delete and recreate the DataStore for every check case. To delete the DataStore, we are able to do that:
@After
enjoyable cleanup() {
File(testContext.filesDir, "datastore").deleteRecursively()
}
and earlier than each check, we recreate it:
@Earlier than
enjoyable setup() {
dataStore = DataStoreFactory.create(
produceFile = {
testContext.dataStoreFile(TEST_DATA_STORE_FILE_NAME)
},
serializer = MyItemSerializer
)
}
To see a full instance, you may go here.
On this article, I needed to indicate the define of a how a Proto DataStore could be examined.
Whereas I went over two check circumstances, relying in your DataStore and the kinds you configured there, there might be extra check circumstances and eventualities to write down. The constructing blocks are there, you simply must adapt it to your wants.