Merge pull request #5604 from element-hq/feature/bma/noAnalytics

Add missing tests on the analytic modules
This commit is contained in:
Benoit Marty
2025-10-24 11:03:53 +02:00
committed by GitHub
7 changed files with 163 additions and 10 deletions

View File

@@ -33,5 +33,7 @@ dependencies {
testCommonDependencies(libs)
testImplementation(projects.libraries.sessionStorage.test)
testImplementation(projects.services.analytics.test)
testImplementation(projects.services.analyticsproviders.test)
testImplementation(projects.services.toolbox.test)
}

View File

@@ -24,7 +24,7 @@ import io.element.android.services.toolbox.api.systemclock.SystemClock
@ContributesBinding(AppScope::class)
class DefaultScreenTracker(
private val analyticsService: AnalyticsService,
private val systemClock: SystemClock
private val systemClock: SystemClock,
) : ScreenTracker {
@Composable
override fun TrackScreen(

View File

@@ -0,0 +1,61 @@
/*
* Copyright 2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.services.analytics.impl
import androidx.lifecycle.Lifecycle
import app.cash.molecule.RecompositionMode
import app.cash.molecule.moleculeFlow
import app.cash.turbine.test
import com.google.common.truth.Truth.assertThat
import im.vector.app.features.analytics.plan.MobileScreen
import io.element.android.services.analytics.api.AnalyticsService
import io.element.android.services.analytics.test.FakeAnalyticsService
import io.element.android.services.toolbox.api.systemclock.SystemClock
import io.element.android.services.toolbox.test.systemclock.FakeSystemClock
import io.element.android.tests.testutils.FakeLifecycleOwner
import io.element.android.tests.testutils.withFakeLifecycleOwner
import kotlinx.coroutines.test.runTest
import org.junit.Test
class DefaultScreenTrackerTest {
@Test
fun `TrackScreen is working as expected`() = runTest {
val analyticsService = FakeAnalyticsService()
val systemClock = FakeSystemClock(150)
val lifecycleOwner = FakeLifecycleOwner()
val sut = createDefaultScreenTracker(
analyticsService = analyticsService,
systemClock = systemClock,
)
moleculeFlow(RecompositionMode.Immediate) {
withFakeLifecycleOwner(lifecycleOwner) {
sut.TrackScreen(MobileScreen.ScreenName.RoomMembers)
}
}.test {
// Screen resumes
lifecycleOwner.givenState(Lifecycle.State.RESUMED)
assertThat(awaitItem()).isEqualTo(Unit)
systemClock.epochMillisResult = 450
lifecycleOwner.givenState(Lifecycle.State.DESTROYED)
}
assertThat(analyticsService.screenEvents).containsExactly(
MobileScreen(
screenName = MobileScreen.ScreenName.RoomMembers,
durationMs = 300,
)
)
}
}
private fun createDefaultScreenTracker(
analyticsService: AnalyticsService = FakeAnalyticsService(),
systemClock: SystemClock = FakeSystemClock(),
) = DefaultScreenTracker(
analyticsService = analyticsService,
systemClock = systemClock,
)

View File

@@ -1,4 +1,5 @@
import extension.setupDependencyInjection
import extension.testCommonDependencies
/*
* Copyright 2023, 2024 New Vector Ltd.
@@ -20,4 +21,5 @@ dependencies {
implementation(projects.libraries.architecture)
implementation(projects.libraries.di)
api(projects.services.analytics.api)
testCommonDependencies(libs)
}

View File

@@ -0,0 +1,67 @@
/*
* Copyright 2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.services.analytics.noop
import app.cash.turbine.test
import com.google.common.truth.Truth.assertThat
import im.vector.app.features.analytics.plan.CallStarted
import im.vector.app.features.analytics.plan.MobileScreen
import im.vector.app.features.analytics.plan.SuperProperties
import im.vector.app.features.analytics.plan.UserProperties
import kotlinx.coroutines.test.runTest
import org.junit.Test
class NoopAnalyticsServiceTest {
@Test
fun `getAvailableAnalyticsProviders returns emptySet`() {
val sut = NoopAnalyticsService()
assertThat(sut.getAvailableAnalyticsProviders()).isEmpty()
}
@Test
fun `didAskUserConsentFlow emits only true`() = runTest {
val sut = NoopAnalyticsService()
sut.didAskUserConsentFlow.test {
assertThat(awaitItem()).isTrue()
awaitComplete()
}
}
@Test
fun `analyticsIdFlow emits only empty string`() = runTest {
val sut = NoopAnalyticsService()
sut.analyticsIdFlow.test {
assertThat(awaitItem()).isEmpty()
sut.setAnalyticsId("anId")
awaitComplete()
}
}
@Test
fun `userConsentFlow emits only false`() = runTest {
val sut = NoopAnalyticsService()
sut.userConsentFlow.test {
assertThat(awaitItem()).isFalse()
awaitComplete()
}
}
@Test
fun `test no op methods`() = runTest {
val sut = NoopAnalyticsService()
sut.setUserConsent(false)
sut.setUserConsent(true)
sut.setDidAskUserConsent()
sut.setAnalyticsId("anId")
sut.capture(CallStarted(true, 1, true))
sut.screen(MobileScreen(screenName = MobileScreen.ScreenName.RoomMembers))
sut.updateUserProperties(UserProperties())
sut.trackError(Exception("an_error"))
sut.updateSuperProperties(SuperProperties())
}
}

View File

@@ -0,0 +1,28 @@
/*
* Copyright 2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.services.analytics.noop
import app.cash.molecule.RecompositionMode
import app.cash.molecule.moleculeFlow
import app.cash.turbine.test
import com.google.common.truth.Truth.assertThat
import im.vector.app.features.analytics.plan.MobileScreen
import kotlinx.coroutines.test.runTest
import org.junit.Test
class NoopScreenTrackerTest {
@Test
fun `TrackScreen is no op`() = runTest {
val sut = NoopScreenTracker()
moleculeFlow(RecompositionMode.Immediate) {
sut.TrackScreen(MobileScreen.ScreenName.RoomMembers)
}.test {
assertThat(awaitItem()).isEqualTo(Unit)
}
}
}

View File

@@ -12,8 +12,6 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.InternalComposeApi
import androidx.compose.runtime.Stable
import androidx.compose.runtime.currentComposer
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LifecycleRegistry
@@ -26,8 +24,6 @@ import io.element.android.libraries.architecture.Presenter
/**
* Composable that provides a fake [LifecycleOwner] to the composition.
*
* **WARNING: DO NOT USE OUTSIDE TESTS.**
*/
@OptIn(InternalComposeApi::class)
@Stable
@@ -44,19 +40,16 @@ fun <T> withFakeLifecycleOwner(
/**
* Test a [Presenter] with a fake [LifecycleOwner].
*
* **WARNING: DO NOT USE OUTSIDE TESTS.**
*/
suspend fun <T> Presenter<T>.testWithLifecycleOwner(
lifecycleOwner: FakeLifecycleOwner = FakeLifecycleOwner(),
block: suspend TurbineTestContext<T>.() -> Unit
) {
moleculeFlow(RecompositionMode.Immediate) {
val ret = withFakeLifecycleOwner(lifecycleOwner) {
withFakeLifecycleOwner(lifecycleOwner) {
present()
}
ret
}.test<T>(validate = block)
}.test(validate = block)
}
@SuppressLint("VisibleForTests")