[go: nahoru, domu]

PlayServiceDevicePerformance : Add Android unit test

Added playServiceDevicePerformanceClassTest android test
to test PlayServiceDevicePerformance library.

Relnote: "Added new constructor for dependency injection during testing"

Test: androidx.core.performance.play.services.PlayServicesDevicePerformanceTest

Change-Id: Ie22020634571f4a304632fd9e43eaee81f34a949
diff --git a/core/core-performance-play-services/build.gradle b/core/core-performance-play-services/build.gradle
index a860881..4dc18a8 100644
--- a/core/core-performance-play-services/build.gradle
+++ b/core/core-performance-play-services/build.gradle
@@ -29,6 +29,16 @@
     implementation(libs.kotlinCoroutinesCore)
     implementation(project(":core:core-performance"))
     implementation("androidx.datastore:datastore-preferences:1.0.0")
+    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4")
+
+    testImplementation(libs.robolectric)
+    androidTestImplementation(libs.junit)
+    androidTestImplementation(libs.testExtJunit)
+    androidTestImplementation(libs.testRunner)
+    androidTestImplementation(libs.truth)
+    androidTestImplementation(libs.espressoCore, excludes.espresso)
+    androidTestImplementation(libs.mockitoAndroid)
+
 
     testImplementation(libs.testCore)
     testImplementation(libs.kotlinStdlib)
diff --git a/core/core-performance-play-services/src/androidTest/java/androidx/core/performance/play/services/PlayServiceDevicePerformanceAndroidTest.kt b/core/core-performance-play-services/src/androidTest/java/androidx/core/performance/play/services/PlayServiceDevicePerformanceAndroidTest.kt
new file mode 100644
index 0000000..033e68a
--- /dev/null
+++ b/core/core-performance-play-services/src/androidTest/java/androidx/core/performance/play/services/PlayServiceDevicePerformanceAndroidTest.kt
@@ -0,0 +1,144 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.core.performance.play.services
+
+import android.app.Application
+import android.content.Context
+import androidx.core.performance.DefaultDevicePerformance
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import com.google.android.gms.common.api.Api
+import com.google.android.gms.common.api.ApiException
+import com.google.android.gms.common.api.Status
+import com.google.android.gms.common.api.internal.ApiKey
+import com.google.android.gms.deviceperformance.DevicePerformanceClient
+import com.google.android.gms.tasks.Task
+import com.google.android.gms.tasks.Tasks
+import com.google.common.truth.Truth
+import java.util.concurrent.TimeUnit
+import kotlinx.coroutines.runBlocking
+import org.junit.After
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.`when`
+
+/** Android Unit tests for [PlayServicesDevicePerformance]. */
+@RunWith(AndroidJUnit4::class)
+class PlayServicesDevicePerformanceTest {
+    open class DevicePerformanceClientTest : DevicePerformanceClient {
+        override fun getApiKey(): ApiKey<Api.ApiOptions.NoOptions> {
+            // method for testing purpose
+            return this.apiKey
+        }
+
+        override fun mediaPerformanceClass(): Task<Int> {
+            return Tasks.forResult(0)
+        }
+    }
+
+    private val context: Context = ApplicationProvider.getApplicationContext<Application>()
+    private val defaultMediaPerformanceClass = DefaultDevicePerformance().mediaPerformanceClass
+
+    @After
+    fun tearDown() = runBlocking {
+        PlayServicesDevicePerformance.clearPerformanceClass(context)
+    }
+
+    @Test
+    @MediumTest
+    fun basePlayServiceDevicePerformanceClassTest() {
+        val playServicesDevicePerformance = PlayServicesDevicePerformance(
+            context
+        )
+        val pcScore = playServicesDevicePerformance.mediaPerformanceClass
+        Truth.assertThat(pcScore).isEqualTo(defaultMediaPerformanceClass)
+    }
+
+    @Test
+    @MediumTest
+    fun mockPlayServiceDevicePerformanceClassTest() {
+        val mockClient: DevicePerformanceClient = mock(DevicePerformanceClientTest::class.java)
+        val mediaPerformanceClass = 33
+        `when`(mockClient.mediaPerformanceClass()).thenAnswer {
+            Tasks.forResult(mediaPerformanceClass)
+        }
+        val playServicesDevicePerformance = PlayServicesDevicePerformance(
+            context,
+            mockClient
+        )
+        delayRead()
+        val pcScore = playServicesDevicePerformance.mediaPerformanceClass
+        Truth.assertThat(pcScore).isEqualTo(mediaPerformanceClass)
+    }
+
+    @Test
+    @MediumTest
+    fun delayMockPlayServiceDevicePerformanceClassTest() {
+        val mockClient: DevicePerformanceClient = mock(DevicePerformanceClientTest::class.java)
+
+        // Delay the response from mockClient.mediaPerformanceClass() so
+        // response will be different that provided.
+        `when`(mockClient.mediaPerformanceClass()).thenAnswer {
+            TimeUnit.SECONDS.sleep(5)
+            Tasks.forResult(defaultMediaPerformanceClass + 100)
+        }
+        val playServicesDevicePerformance = PlayServicesDevicePerformance(
+            context,
+            mockClient
+        )
+        val pcScore = playServicesDevicePerformance.mediaPerformanceClass
+        Truth.assertThat(pcScore).isEqualTo(defaultMediaPerformanceClass)
+    }
+
+    @Test
+    @MediumTest
+    fun playServiceCrashPerformanceClassTest() {
+        val mockClient: DevicePerformanceClient = mock(DevicePerformanceClientTest::class.java)
+        `when`(mockClient.mediaPerformanceClass()).thenReturn( // Throw an exception here.
+            Tasks.forException(IllegalStateException())
+        )
+        val pc = PlayServicesDevicePerformance(
+            context,
+            mockClient
+        )
+        // Since the gms service has crashed, the library should still return default value.
+        Truth.assertThat(pc.mediaPerformanceClass).isEqualTo(defaultMediaPerformanceClass)
+    }
+
+    @Test
+    @MediumTest
+    fun playServiceNotStartPerformanceClassTest() {
+        val mockClient: DevicePerformanceClient = mock(DevicePerformanceClientTest::class.java)
+        `when`(mockClient.mediaPerformanceClass()).thenReturn( // Throw an exception here.
+            Tasks.forException(ApiException(Status.RESULT_TIMEOUT))
+        )
+        val pc = PlayServicesDevicePerformance(
+            context,
+            mockClient
+        )
+        // Since the gms service not started, the library should still return default value.
+        Truth.assertThat(pc.mediaPerformanceClass).isEqualTo(defaultMediaPerformanceClass)
+    }
+
+    /* Add delay to make sure that value is written in Preference datastore before reading it */
+    private fun delayRead() {
+        val delayTime: Long = 200
+        TimeUnit.MILLISECONDS.sleep(delayTime)
+    }
+}
diff --git a/core/core-performance-play-services/src/main/java/androidx/core/performance/play/services/PlayServicesDevicePerformance.kt b/core/core-performance-play-services/src/main/java/androidx/core/performance/play/services/PlayServicesDevicePerformance.kt
index cf919ba..2fa0a804 100644
--- a/core/core-performance-play-services/src/main/java/androidx/core/performance/play/services/PlayServicesDevicePerformance.kt
+++ b/core/core-performance-play-services/src/main/java/androidx/core/performance/play/services/PlayServicesDevicePerformance.kt
@@ -18,13 +18,16 @@
 
 import android.content.Context
 import android.util.Log
+import androidx.annotation.VisibleForTesting
 import androidx.core.performance.DefaultDevicePerformance
 import androidx.core.performance.DevicePerformance
 import androidx.datastore.preferences.core.edit
 import androidx.datastore.preferences.core.intPreferencesKey
 import androidx.datastore.preferences.preferencesDataStore
+import com.google.android.gms.common.api.ApiException
 import com.google.android.gms.deviceperformance.DevicePerformanceClient
 import kotlin.math.max
+import kotlinx.coroutines.CompletableDeferred
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.first
 import kotlinx.coroutines.flow.map
@@ -40,6 +43,7 @@
     private val tag = "PlayServicesDevicePerformance"
 
     private val defaultMpc = DefaultDevicePerformance()
+    private val playServicesValueStoredDeferred = CompletableDeferred<Boolean>()
 
     override val mediaPerformanceClass get() = lazyMpc.value
     private val lazyMpc =
@@ -54,26 +58,38 @@
             }
         }
 
-    private val Context.performanceStore by preferencesDataStore(name = "media_performance_class")
-    private val mpcKey = intPreferencesKey("mpc_value")
-
-    private val client: DevicePerformanceClient =
-        com.google.android.gms.deviceperformance.DevicePerformance.getClient(context)
-
     init {
         Log.v(
             tag,
             "Getting mediaPerformanceClass from " +
                 "com.google.android.gms.deviceperformance.DevicePerformanceClient"
         )
-        client.mediaPerformanceClass().addOnSuccessListener { result ->
-            runBlocking {
-                Log.v(tag, "Got mediaPerformanceClass $result")
-                val storedVal = max(result, defaultMpc.mediaPerformanceClass)
-                launch {
-                    savePerformanceClass(storedVal)
-                    Log.v(tag, "Saved mediaPerformanceClass $storedVal")
-                }
+        updatePerformanceStore(
+            com.google.android.gms.deviceperformance.DevicePerformance.getClient(context)
+        )
+    }
+
+    @VisibleForTesting
+    internal constructor(context: Context, client: DevicePerformanceClient) : this(context) {
+        // mock client should wait for the playServices client to finish,
+        // so the test results are determined by the mock client.
+        runBlocking {
+            playServicesValueStoredDeferred.await()
+        }
+        updatePerformanceStore(client)
+    }
+
+    private val mpcKey = intPreferencesKey("mpc_value")
+
+    internal companion object {
+        // To avoid creating multiple instance of datastore
+        private val Context.performanceStore by
+        preferencesDataStore(name = "media_performance_class")
+
+        @VisibleForTesting
+        suspend fun clearPerformanceClass(context: Context) {
+            context.performanceStore.edit {
+                it.clear()
             }
         }
     }
@@ -90,4 +106,25 @@
             values[mpcKey] = value
         }
     }
+
+    private fun updatePerformanceStore(client: DevicePerformanceClient) {
+        client.mediaPerformanceClass().addOnSuccessListener { result ->
+            runBlocking {
+                Log.v(tag, "Got mediaPerformanceClass $result")
+                val storedVal = max(result, defaultMpc.mediaPerformanceClass)
+                launch {
+                    savePerformanceClass(storedVal)
+                    Log.v(tag, "Saved mediaPerformanceClass $storedVal")
+                    playServicesValueStoredDeferred.complete(true)
+                }
+            }
+        }.addOnFailureListener { e: Exception ->
+            if (e is ApiException) {
+                Log.e(tag, "Error saving mediaPerformanceClass: $e")
+            } else if (e is IllegalStateException) {
+                Log.e(tag, "Error saving mediaPerformanceClass: $e")
+            }
+            playServicesValueStoredDeferred.complete(true)
+        }
+    }
 }