Browse Source

Add test on DefaultAnalyticsService

pull/2933/head
Benoit Marty 4 months ago
parent
commit
15dc423345
  1. 6
      services/analytics/impl/build.gradle.kts
  2. 286
      services/analytics/impl/src/test/kotlin/io/element/android/services/analytics/impl/DefaultAnalyticsServiceTest.kt
  3. 47
      services/analytics/impl/src/test/kotlin/io/element/android/services/analytics/impl/store/FakeAnalyticsStore.kt
  4. 27
      services/analyticsproviders/test/build.gradle.kts
  5. 41
      services/analyticsproviders/test/src/main/kotlin/io/element/android/services/analyticsproviders/test/FakeAnalyticsProvider.kt

6
services/analytics/impl/build.gradle.kts

@ -43,5 +43,9 @@ dependencies { @@ -43,5 +43,9 @@ dependencies {
implementation(libs.androidx.datastore.preferences)
testImplementation(libs.coroutines.test)
testImplementation(libs.test.mockk)
testImplementation(libs.test.junit)
testImplementation(libs.test.truth)
testImplementation(projects.libraries.sessionStorage.test)
testImplementation(projects.services.analyticsproviders.test)
testImplementation(projects.tests.testutils)
}

286
services/analytics/impl/src/test/kotlin/io/element/android/services/analytics/impl/DefaultAnalyticsServiceTest.kt

@ -0,0 +1,286 @@ @@ -0,0 +1,286 @@
/*
* Copyright (c) 2024 New Vector Ltd
*
* 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.
*/
@file:OptIn(ExperimentalCoroutinesApi::class)
package io.element.android.services.analytics.impl
import com.google.common.truth.Truth.assertThat
import im.vector.app.features.analytics.itf.VectorAnalyticsEvent
import im.vector.app.features.analytics.itf.VectorAnalyticsScreen
import im.vector.app.features.analytics.plan.MobileScreen
import im.vector.app.features.analytics.plan.PollEnd
import im.vector.app.features.analytics.plan.UserProperties
import io.element.android.libraries.sessionstorage.api.observer.SessionObserver
import io.element.android.libraries.sessionstorage.test.observer.NoOpSessionObserver
import io.element.android.services.analytics.impl.store.AnalyticsStore
import io.element.android.services.analytics.impl.store.FakeAnalyticsStore
import io.element.android.services.analyticsproviders.api.AnalyticsProvider
import io.element.android.services.analyticsproviders.test.FakeAnalyticsProvider
import io.element.android.tests.testutils.lambda.lambdaRecorder
import io.element.android.tests.testutils.lambda.value
import io.element.android.tests.testutils.runCancellableScopeTest
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.cancel
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.runTest
import org.junit.Test
class DefaultAnalyticsServiceTest {
@Test
fun `getAvailableAnalyticsProviders return the set of provider`() = runCancellableScopeTest {
val providers = setOf(
FakeAnalyticsProvider(name = "provider1", stopLambda = { }),
FakeAnalyticsProvider(name = "provider2", stopLambda = { }),
)
val sut = createDefaultAnalyticsService(
coroutineScope = it,
analyticsProviders = providers
)
val result = sut.getAvailableAnalyticsProviders()
assertThat(result).isEqualTo(providers)
}
@Test
fun `when consent is not provided, capture is no op`() = runCancellableScopeTest {
val sut = createDefaultAnalyticsService(it)
sut.capture(anEvent)
}
@Test
fun `when consent is provided, capture is sent to the AnalyticsProvider`() = runCancellableScopeTest {
val initLambda = lambdaRecorder<Unit> { }
val captureLambda = lambdaRecorder<VectorAnalyticsEvent, Unit> { _ -> }
val sut = createDefaultAnalyticsService(
coroutineScope = it,
analyticsStore = FakeAnalyticsStore(defaultUserConsent = true),
analyticsProviders = setOf(
FakeAnalyticsProvider(
initLambda = initLambda,
captureLambda = captureLambda,
)
)
)
initLambda.assertions().isCalledOnce()
sut.capture(anEvent)
captureLambda.assertions().isCalledOnce().with(value(anEvent))
}
@Test
fun `when consent is not provided, screen is no op`() = runCancellableScopeTest {
val sut = createDefaultAnalyticsService(it)
sut.screen(aScreen)
}
@Test
fun `when consent is provided, screen is sent to the AnalyticsProvider`() = runCancellableScopeTest {
val initLambda = lambdaRecorder<Unit> { }
val screenLambda = lambdaRecorder<VectorAnalyticsScreen, Unit> { _ -> }
val sut = createDefaultAnalyticsService(
coroutineScope = it,
analyticsStore = FakeAnalyticsStore(defaultUserConsent = true),
analyticsProviders = setOf(
FakeAnalyticsProvider(
initLambda = initLambda,
screenLambda = screenLambda,
)
)
)
initLambda.assertions().isCalledOnce()
sut.screen(aScreen)
screenLambda.assertions().isCalledOnce().with(value(aScreen))
}
@Test
fun `when consent is not provided, trackError is no op`() = runCancellableScopeTest {
val sut = createDefaultAnalyticsService(it)
sut.trackError(anError)
}
@Test
fun `when consent is provided, trackError is sent to the AnalyticsProvider`() = runCancellableScopeTest {
val initLambda = lambdaRecorder<Unit> { }
val trackErrorLambda = lambdaRecorder<Throwable, Unit> { _ -> }
val sut = createDefaultAnalyticsService(
coroutineScope = it,
analyticsStore = FakeAnalyticsStore(defaultUserConsent = true),
analyticsProviders = setOf(
FakeAnalyticsProvider(
initLambda = initLambda,
trackErrorLambda = trackErrorLambda,
)
)
)
initLambda.assertions().isCalledOnce()
sut.trackError(anError)
trackErrorLambda.assertions().isCalledOnce().with(value(anError))
}
@Test
fun `setUserConsent is sent to the store`() = runCancellableScopeTest {
val store = FakeAnalyticsStore()
val sut = createDefaultAnalyticsService(
coroutineScope = it,
analyticsStore = store,
)
assertThat(store.userConsentFlow.first()).isFalse()
assertThat(sut.getUserConsent().first()).isFalse()
sut.setUserConsent(true)
assertThat(store.userConsentFlow.first()).isTrue()
assertThat(sut.getUserConsent().first()).isTrue()
}
@Test
fun `setAnalyticsId is sent to the store`() = runCancellableScopeTest {
val store = FakeAnalyticsStore()
val sut = createDefaultAnalyticsService(
coroutineScope = it,
analyticsStore = store,
)
assertThat(store.analyticsIdFlow.first()).isEqualTo("")
assertThat(sut.getAnalyticsId().first()).isEqualTo("")
sut.setAnalyticsId(anId)
assertThat(store.analyticsIdFlow.first()).isEqualTo(anId)
assertThat(sut.getAnalyticsId().first()).isEqualTo(anId)
}
@Test
fun `setDidAskUserConsent is sent to the store`() = runCancellableScopeTest {
val store = FakeAnalyticsStore()
val sut = createDefaultAnalyticsService(
coroutineScope = it,
analyticsStore = store,
)
assertThat(store.didAskUserConsentFlow.first()).isFalse()
assertThat(sut.didAskUserConsent().first()).isFalse()
sut.setDidAskUserConsent()
assertThat(store.didAskUserConsentFlow.first()).isTrue()
assertThat(sut.didAskUserConsent().first()).isTrue()
}
@Test
fun `when a session is deleted, the store is reset`() = runCancellableScopeTest {
val resetLambda = lambdaRecorder<Unit> { }
val store = FakeAnalyticsStore(
resetLambda = resetLambda,
)
val sut = createDefaultAnalyticsService(
coroutineScope = it,
analyticsStore = store,
)
sut.onSessionDeleted("userId")
resetLambda.assertions().isCalledOnce()
}
@Test
fun `when reset is invoked, the user consent is reset`() = runCancellableScopeTest {
val store = FakeAnalyticsStore(
defaultDidAskUserConsent = true,
)
val sut = createDefaultAnalyticsService(
coroutineScope = it,
analyticsStore = store,
)
assertThat(store.didAskUserConsentFlow.first()).isTrue()
sut.reset()
assertThat(store.didAskUserConsentFlow.first()).isFalse()
}
@Test
fun `when a session is added, nothing happen`() = runCancellableScopeTest {
val sut = createDefaultAnalyticsService(
coroutineScope = it,
)
sut.onSessionCreated("userId")
}
@Test
fun `when consent is not provided, updateUserProperties is stored for future use`() = runTest {
val completable = CompletableDeferred<Unit>()
val updateUserPropertiesLambda = lambdaRecorder<UserProperties, Unit> { _ ->
completable.complete(Unit)
}
launch {
val sut = createDefaultAnalyticsService(
coroutineScope = this,
analyticsProviders = setOf(
FakeAnalyticsProvider(
initLambda = { },
stopLambda = { },
updateUserPropertiesLambda = updateUserPropertiesLambda,
)
)
)
sut.updateUserProperties(aUserProperty)
updateUserPropertiesLambda.assertions().isNeverCalled()
// Give user consent
sut.setUserConsent(true)
completable.await()
updateUserPropertiesLambda.assertions().isCalledOnce().with(value(aUserProperty))
cancel()
}
}
@Test
fun `when consent is provided, updateUserProperties is sent to the provider`() = runCancellableScopeTest {
val updateUserPropertiesLambda = lambdaRecorder<UserProperties, Unit> { _ -> }
val sut = createDefaultAnalyticsService(
coroutineScope = it,
analyticsProviders = setOf(
FakeAnalyticsProvider(
initLambda = { },
updateUserPropertiesLambda = updateUserPropertiesLambda,
)
),
analyticsStore = FakeAnalyticsStore(defaultUserConsent = true),
)
sut.updateUserProperties(aUserProperty)
updateUserPropertiesLambda.assertions().isCalledOnce().with(value(aUserProperty))
}
private suspend fun createDefaultAnalyticsService(
coroutineScope: CoroutineScope,
analyticsProviders: Set<@JvmSuppressWildcards AnalyticsProvider> = setOf(
FakeAnalyticsProvider(
stopLambda = { },
)
),
analyticsStore: AnalyticsStore = FakeAnalyticsStore(),
sessionObserver: SessionObserver = NoOpSessionObserver(),
) = DefaultAnalyticsService(
analyticsProviders = analyticsProviders,
analyticsStore = analyticsStore,
coroutineScope = coroutineScope,
sessionObserver = sessionObserver,
).also {
// Wait for the service to be ready
delay(1)
}
private companion object {
private val anEvent = PollEnd()
private val aScreen = MobileScreen(screenName = MobileScreen.ScreenName.User)
private val aUserProperty = UserProperties(
ftueUseCaseSelection = UserProperties.FtueUseCaseSelection.WorkMessaging,
)
private val anError = Exception("a reason")
private val anId = "anId"
}
}

47
services/analytics/impl/src/test/kotlin/io/element/android/services/analytics/impl/store/FakeAnalyticsStore.kt

@ -0,0 +1,47 @@ @@ -0,0 +1,47 @@
/*
* Copyright (c) 2024 New Vector Ltd
*
* 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 io.element.android.services.analytics.impl.store
import io.element.android.tests.testutils.lambda.lambdaError
import kotlinx.coroutines.flow.MutableStateFlow
class FakeAnalyticsStore(
defaultUserConsent: Boolean = false,
defaultDidAskUserConsent: Boolean = false,
defaultAnalyticsId: String = "",
private val resetLambda: () -> Unit = { lambdaError() },
) : AnalyticsStore {
override val userConsentFlow = MutableStateFlow(defaultUserConsent)
override val didAskUserConsentFlow = MutableStateFlow(defaultDidAskUserConsent)
override val analyticsIdFlow = MutableStateFlow(defaultAnalyticsId)
override suspend fun setUserConsent(newUserConsent: Boolean) {
userConsentFlow.emit(newUserConsent)
}
override suspend fun setDidAskUserConsent(newValue: Boolean) {
didAskUserConsentFlow.emit(newValue)
}
override suspend fun setAnalyticsId(newAnalyticsId: String) {
analyticsIdFlow.emit(newAnalyticsId)
}
override suspend fun reset() {
resetLambda()
}
}

27
services/analyticsproviders/test/build.gradle.kts

@ -0,0 +1,27 @@ @@ -0,0 +1,27 @@
/*
* Copyright (c) 2024 New Vector Ltd
*
* 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.
*/
plugins {
id("io.element.android-library")
}
android {
namespace = "io.element.android.services.analyticsproviders.test"
}
dependencies {
implementation(projects.services.analyticsproviders.api)
implementation(projects.tests.testutils)
}

41
services/analyticsproviders/test/src/main/kotlin/io/element/android/services/analyticsproviders/test/FakeAnalyticsProvider.kt

@ -0,0 +1,41 @@ @@ -0,0 +1,41 @@
/*
* Copyright (c) 2024 New Vector Ltd
*
* 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 io.element.android.services.analyticsproviders.test
import im.vector.app.features.analytics.itf.VectorAnalyticsEvent
import im.vector.app.features.analytics.itf.VectorAnalyticsScreen
import im.vector.app.features.analytics.plan.UserProperties
import io.element.android.services.analyticsproviders.api.AnalyticsProvider
import io.element.android.tests.testutils.lambda.lambdaError
class FakeAnalyticsProvider(
override val name: String = "FakeAnalyticsProvider",
private val initLambda: () -> Unit = { lambdaError() },
private val stopLambda: () -> Unit = { lambdaError() },
private val captureLambda: (VectorAnalyticsEvent) -> Unit = { lambdaError() },
private val screenLambda: (VectorAnalyticsScreen) -> Unit = { lambdaError() },
private val updateUserPropertiesLambda: (UserProperties) -> Unit = { lambdaError() },
private val trackErrorLambda: (Throwable) -> Unit = { lambdaError() }
) : AnalyticsProvider {
override fun init() = initLambda()
override fun stop() = stopLambda()
override fun capture(event: VectorAnalyticsEvent) = captureLambda(event)
override fun screen(screen: VectorAnalyticsScreen) = screenLambda(screen)
override fun updateUserProperties(userProperties: UserProperties) = updateUserPropertiesLambda(userProperties)
override fun trackError(throwable: Throwable) = trackErrorLambda(throwable)
}
Loading…
Cancel
Save