Skip to content

Commit ff43918

Browse files
author
Krzysztof Borowy
committed
tests
1 parent 0c2ca34 commit ff43918

File tree

10 files changed

+404
-40
lines changed

10 files changed

+404
-40
lines changed

android/build.gradle

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ buildscript {
5555
// If you really need bigger size, please keep in mind the potential consequences.
5656
long dbSizeInMB = 6L
5757
def newDbSize = rootProject.properties['AsyncStorage_db_size_in_MB']
58-
if( newDbSize != null && newDbSize.isLong()) {
58+
if (newDbSize != null && newDbSize.isLong()) {
5959
dbSizeInMB = newDbSize.toLong()
6060
}
6161

@@ -64,9 +64,10 @@ def useDedicatedExecutor = getFlagOrDefault('AsyncStorage_dedicatedExecutor', fa
6464
def useNextStorage = getFlagOrDefault("AsyncStorage_useNextStorage", false)
6565

6666
apply plugin: 'com.android.library'
67-
if(useNextStorage) {
67+
if (useNextStorage) {
6868
apply plugin: 'kotlin-android'
6969
apply plugin: 'kotlin-kapt'
70+
apply from: './testresults.gradle'
7071
}
7172

7273
android {
@@ -82,6 +83,12 @@ android {
8283
lintOptions {
8384
abortOnError false
8485
}
86+
87+
if (useNextStorage) {
88+
testOptions {
89+
unitTests.returnDefaultValues = true
90+
}
91+
}
8592
}
8693

8794
repositories {
@@ -95,13 +102,26 @@ repositories {
95102

96103
dependencies {
97104

98-
if(useNextStorage) {
105+
if (useNextStorage) {
99106
def room_version = "2.2.6"
100-
def coroutines_version = "1.3.9"
107+
def coroutines_version = "1.4.2"
108+
def junit_version = "4.12"
109+
def robolectric_version = "4.2.1"
110+
def truth_version = "1.1.2"
111+
def androidxtest_version = "1.1.0"
112+
def coroutinesTest_version = "1.4.2"
101113

102114
implementation "androidx.room:room-runtime:$room_version"
103115
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
104116
kapt "androidx.room:room-compiler:$room_version"
117+
118+
testImplementation "junit:junit:$junit_version"
119+
testImplementation "androidx.test:runner:$androidxtest_version"
120+
testImplementation "androidx.test:rules:$androidxtest_version"
121+
testImplementation "androidx.test.ext:junit:$androidxtest_version"
122+
testImplementation "org.robolectric:robolectric:$robolectric_version"
123+
testImplementation "com.google.truth:truth:$truth_version"
124+
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutinesTest_version"
105125
}
106126

107127
//noinspection GradleDynamicVersion

android/gradle.properties

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
android.useAndroidX=true

android/src/main/java/com/reactnativecommunity/asyncstorage/next/ArgumentHelpers.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ fun ReadableArray.toKeyList(): List<String> {
3535

3636
for (item in list) {
3737
if (item !is String) {
38-
throw AsyncStorageError.keyIsNull()
38+
throw AsyncStorageError.keyNotString()
3939
}
4040
}
4141
return list as List<String>

android/src/main/java/com/reactnativecommunity/asyncstorage/next/StorageSupplier.kt

Lines changed: 15 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,19 @@ data class Entry(
3131
)
3232

3333
@Dao
34-
private interface StorageDao {
34+
internal interface StorageDao {
35+
36+
@Transaction
37+
@Query("SELECT * FROM $TABLE_NAME WHERE `$COLUMN_KEY` IN (:keys)")
38+
fun getValues(keys: List<String>): List<Entry>
39+
40+
@Transaction
41+
@Insert(onConflict = OnConflictStrategy.REPLACE)
42+
fun setValues(entries: List<Entry>)
43+
44+
@Transaction
45+
@Query("DELETE FROM $TABLE_NAME WHERE `$COLUMN_KEY` in (:keys)")
46+
fun removeValues(keys: List<String>)
3547

3648
@Transaction
3749
fun mergeValues(entries: List<Entry>) {
@@ -53,37 +65,13 @@ private interface StorageDao {
5365
setValues(newEntries)
5466
}
5567

56-
@Transaction
57-
@Query("SELECT * FROM $TABLE_NAME WHERE `$COLUMN_KEY` IN (:keys)")
58-
fun getValues(keys: List<String>): List<Entry>
59-
60-
@Transaction
61-
fun setValues(entries: List<Entry>) {
62-
val insertResult = insert(entries)
63-
with(entries.filterIndexed { i, _ -> insertResult[i] == -1L }) {
64-
update(this)
65-
}
66-
}
67-
68-
@Transaction
69-
@Query("DELETE FROM $TABLE_NAME WHERE `$COLUMN_KEY` in (:keys)")
70-
fun removeValues(keys: List<String>)
71-
7268
@Transaction
7369
@Query("SELECT `$COLUMN_KEY` FROM $TABLE_NAME")
7470
fun getKeys(): List<String>
7571

7672
@Transaction
7773
@Query("DELETE FROM $TABLE_NAME")
7874
fun clear()
79-
80-
81-
// insert and update are components of setValues - not to be used separately
82-
@Insert(onConflict = OnConflictStrategy.IGNORE)
83-
fun insert(entries: List<Entry>): List<Long>
84-
85-
@Update
86-
fun update(entries: List<Entry>)
8775
}
8876

8977

@@ -120,7 +108,7 @@ private object MIGRATION_TO_NEXT : Migration(1, 2) {
120108
}
121109

122110
@Database(entities = [Entry::class], version = DATABASE_VERSION, exportSchema = true)
123-
private abstract class StorageDb : RoomDatabase() {
111+
internal abstract class StorageDb : RoomDatabase() {
124112
abstract fun storage(): StorageDao
125113

126114
companion object {
@@ -160,7 +148,7 @@ interface AsyncStorageAccess {
160148
suspend fun mergeValues(entries: List<Entry>)
161149
}
162150

163-
class StorageSupplier private constructor(db: StorageDb) : AsyncStorageAccess {
151+
class StorageSupplier internal constructor(db: StorageDb) : AsyncStorageAccess {
164152
companion object {
165153
fun getInstance(ctx: Context): AsyncStorageAccess {
166154
return StorageSupplier(StorageDb.getDatabase(ctx))
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
package com.reactnativecommunity.asyncstorage.next
2+
3+
import com.google.common.truth.Truth.assertThat
4+
import org.junit.Assert.assertThrows
5+
import org.junit.Test
6+
import org.junit.runner.RunWith
7+
import org.junit.runners.BlockJUnit4ClassRunner
8+
9+
@RunWith(BlockJUnit4ClassRunner::class)
10+
class ArgumentHelpersTest {
11+
12+
@Test
13+
fun transformsArgumentsToEntryList() {
14+
val keyValue1 = arrayListOf("key1", "value1")
15+
val keyValue2 = arrayListOf("key2", "value2")
16+
val keyValue3 = arrayListOf("key3", "value3")
17+
18+
val args = createNativeCallArguments(keyValue1, keyValue2, keyValue3)
19+
20+
assertThat(args.toEntryList()).isEqualTo(
21+
listOf(
22+
Entry("key1", "value1"),
23+
Entry("key2", "value2"),
24+
Entry("key3", "value3"),
25+
)
26+
)
27+
}
28+
29+
@Test
30+
fun transfersArgumentsToKeyList() {
31+
val keyList = listOf("key1", "key2", "key3")
32+
33+
val args = createNativeCallArguments("key1", "key2", "key3")
34+
35+
assertThat(args.toKeyList()).isEqualTo(keyList)
36+
}
37+
38+
@Test
39+
fun throwsIfArgumentsNotValidFormat() {
40+
val invalid = arrayListOf("invalid")
41+
val args = createNativeCallArguments(invalid)
42+
43+
val error = assertThrows(AsyncStorageError::class.java) {
44+
args.toEntryList()
45+
}
46+
47+
assertThat(error is AsyncStorageError).isTrue()
48+
assertThat(error).hasMessageThat()
49+
.isEqualTo("Invalid key-value format. Expected a list of [key, value] list.")
50+
}
51+
52+
@Test
53+
fun throwsIfArgumentKeyIsNullOrNotString() {
54+
val argsInvalidNull = createNativeCallArguments(arrayListOf(null, "invalid"))
55+
56+
val errorArgsInvalidNull = assertThrows(AsyncStorageError::class.java) {
57+
argsInvalidNull.toEntryList()
58+
}
59+
assertThat(errorArgsInvalidNull is AsyncStorageError).isTrue()
60+
assertThat(errorArgsInvalidNull).hasMessageThat().isEqualTo("Key cannot be null.")
61+
62+
63+
val notStringArgs = createNativeCallArguments(arrayListOf(123, "invalid"))
64+
65+
val errorNotString = assertThrows(AsyncStorageError::class.java) {
66+
notStringArgs.toEntryList()
67+
}
68+
assertThat(errorNotString is AsyncStorageError).isTrue()
69+
assertThat(errorNotString).hasMessageThat()
70+
.isEqualTo("Provided key is not string. Only strings are supported as storage key.")
71+
}
72+
73+
@Test
74+
fun throwsIfArgumentValueNotString() {
75+
val invalidArgs = createNativeCallArguments(arrayListOf("my_key", 666))
76+
77+
val error = assertThrows(AsyncStorageError::class.java) {
78+
invalidArgs.toEntryList()
79+
}
80+
81+
assertThat(error is AsyncStorageError).isTrue()
82+
assertThat(error).hasMessageThat()
83+
.isEqualTo("Value for key \"my_key\" is not a string. Only strings are supported as a value.")
84+
}
85+
}
86+
87+
88+
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
package com.reactnativecommunity.asyncstorage.next
2+
3+
import androidx.room.Room
4+
import androidx.test.ext.junit.runners.AndroidJUnit4
5+
import androidx.test.platform.app.InstrumentationRegistry
6+
import com.google.common.truth.Truth.assertThat
7+
import kotlinx.coroutines.ExperimentalCoroutinesApi
8+
import kotlinx.coroutines.test.runBlockingTest
9+
import org.json.JSONObject
10+
import org.junit.After
11+
import org.junit.Before
12+
import org.junit.Test
13+
import org.junit.runner.RunWith
14+
import kotlin.random.Random
15+
16+
@ExperimentalCoroutinesApi
17+
@RunWith(AndroidJUnit4::class)
18+
class AsyncStorageAccessTest {
19+
20+
private lateinit var asyncStorage: AsyncStorageAccess
21+
private lateinit var database: StorageDb
22+
23+
@Before
24+
fun setup() {
25+
database = Room.inMemoryDatabaseBuilder(
26+
InstrumentationRegistry.getInstrumentation().context, StorageDb::class.java
27+
).allowMainThreadQueries().build()
28+
29+
asyncStorage = StorageSupplier(database)
30+
}
31+
32+
@After
33+
fun tearDown() {
34+
database.close()
35+
}
36+
37+
@Test
38+
fun performsBasicGetSetRemoveOperations() = runBlockingTest {
39+
val entriesCount = 10
40+
val entries = createRandomEntries(entriesCount)
41+
val keys = entries.map { it.key }
42+
43+
assertThat(asyncStorage.getValues(keys)).hasSize(0)
44+
asyncStorage.setValues(entries)
45+
assertThat(asyncStorage.getValues(keys)).hasSize(entriesCount)
46+
47+
val indicesToRemove = (1..4).map { Random.nextInt(0, entriesCount) }.distinct()
48+
val toRemove = entries.filterIndexed { index, _ -> indicesToRemove.contains(index) }
49+
50+
asyncStorage.removeValues(toRemove.map { it.key })
51+
val currentEntries = asyncStorage.getValues(keys)
52+
assertThat(currentEntries).hasSize(entriesCount - toRemove.size)
53+
}
54+
55+
@Test
56+
fun readsAllKeysAndClearsDb() = runBlockingTest {
57+
val entries = createRandomEntries(8)
58+
val keys = entries.map { it.key }
59+
asyncStorage.setValues(entries)
60+
val dbKeys = asyncStorage.getKeys()
61+
assertThat(dbKeys).isEqualTo(keys)
62+
asyncStorage.clear()
63+
assertThat(asyncStorage.getValues(keys)).hasSize(0)
64+
}
65+
66+
@Test
67+
fun mergesDeeplyTwoValues() = runBlockingTest {
68+
val initialEntry = Entry("key", VALUE_INITIAL)
69+
val overrideEntry = Entry("key", VALUE_OVERRIDES)
70+
71+
asyncStorage.setValues(listOf(initialEntry))
72+
asyncStorage.mergeValues(listOf(overrideEntry))
73+
74+
val current = asyncStorage.getValues(listOf("key"))[0]
75+
76+
assertThat(current.value).isEqualTo(VALUE_MERGED)
77+
}
78+
79+
@Test
80+
fun updatesExistingValues() = runBlockingTest {
81+
val key = "test_key"
82+
val value = "test_value"
83+
val entries = listOf(Entry(key, value))
84+
85+
assertThat(asyncStorage.getValues(listOf(key))).hasSize(0)
86+
asyncStorage.setValues(entries)
87+
assertThat(asyncStorage.getValues(listOf(key))).isEqualTo(entries)
88+
89+
val modifiedEntries = listOf(Entry(key, "updatedValue"))
90+
91+
asyncStorage.setValues(modifiedEntries)
92+
assertThat(asyncStorage.getValues(listOf(key))).isEqualTo(modifiedEntries)
93+
}
94+
95+
96+
// Test Helpers
97+
private fun createRandomEntries(count: Int = Random.nextInt(10)): List<Entry> {
98+
val entries = mutableListOf<Entry>()
99+
for (i in 0 until count) {
100+
entries.add(Entry("key$i", "value$i"))
101+
}
102+
return entries
103+
}
104+
105+
private val VALUE_INITIAL = JSONObject(
106+
"""
107+
{
108+
"key":"value",
109+
"key2":"override",
110+
"key3":{
111+
"key4":"value4",
112+
"key6":{
113+
"key7":"value7",
114+
"key8":"override"
115+
}
116+
}
117+
}
118+
""".trimMargin()
119+
).toString()
120+
121+
private val VALUE_OVERRIDES = JSONObject(
122+
"""
123+
{
124+
"key2":"value2",
125+
"key3":{
126+
"key5":"value5",
127+
"key6":{
128+
"key8":"value8"
129+
}
130+
}
131+
}
132+
"""
133+
).toString()
134+
135+
136+
private val VALUE_MERGED = JSONObject(
137+
"""
138+
{
139+
"key":"value",
140+
"key2":"value2",
141+
"key3":{
142+
"key4":"value4",
143+
"key5":"value5",
144+
"key6":{
145+
"key7":"value7",
146+
"key8":"value8"
147+
}
148+
}
149+
}
150+
""".trimMargin()
151+
).toString()
152+
}

0 commit comments

Comments
 (0)