From de19ef63d0cec32d6416c20222e9cf27d4ff87a4 Mon Sep 17 00:00:00 2001 From: Lennoard Date: Thu, 21 Aug 2025 08:02:56 -0300 Subject: [PATCH] feat: add tests framework Signed-off-by: Lennoard --- app/build.gradle.kts | 4 +- data/build.gradle.kts | 2 + .../repository/DocumentationRepositoryImpl.kt | 3 +- .../DocumentationRepositoryImplTest.kt | 117 ++++++++++++++++++ gradle/libs.versions.toml | 3 + 5 files changed, 125 insertions(+), 4 deletions(-) create mode 100644 data/src/test/java/com/androidvip/sysctlgui/data/repository/DocumentationRepositoryImplTest.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index f16316f..1c167ae 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -17,8 +17,8 @@ android { applicationId = AppConfig.appId minSdk = AppConfig.minSdkVersion targetSdk = AppConfig.targetSdkVersion - versionCode = 20 - versionName = "3.0.1" + versionCode = 21 + versionName = "3.0.2" vectorDrawables.useSupportLibrary = true androidResources { localeFilters += listOf("en", "de", "pt-rBR", "tr") diff --git a/data/build.gradle.kts b/data/build.gradle.kts index 56b4f2d..6f5f791 100644 --- a/data/build.gradle.kts +++ b/data/build.gradle.kts @@ -63,4 +63,6 @@ dependencies { ksp(libs.androidx.room.compiler) testImplementation(libs.junit) + testImplementation(libs.mockk.android) + testImplementation(libs.kotlinx.coroutines.test) } diff --git a/data/src/main/java/com/androidvip/sysctlgui/data/repository/DocumentationRepositoryImpl.kt b/data/src/main/java/com/androidvip/sysctlgui/data/repository/DocumentationRepositoryImpl.kt index ee6f3ea..eae930f 100644 --- a/data/src/main/java/com/androidvip/sysctlgui/data/repository/DocumentationRepositoryImpl.kt +++ b/data/src/main/java/com/androidvip/sysctlgui/data/repository/DocumentationRepositoryImpl.kt @@ -5,7 +5,6 @@ import com.androidvip.sysctlgui.domain.models.KernelParam import com.androidvip.sysctlgui.domain.models.ParamDocumentation import com.androidvip.sysctlgui.domain.repository.AppPrefs import com.androidvip.sysctlgui.domain.repository.DocumentationRepository -import kotlinx.coroutines.withTimeout import kotlinx.coroutines.withTimeoutOrNull /** @@ -36,6 +35,6 @@ class DocumentationRepositoryImpl( } companion object { - private const val REQUEST_TIMEOUT_MS = 3000L + internal const val REQUEST_TIMEOUT_MS = 3000L } } diff --git a/data/src/test/java/com/androidvip/sysctlgui/data/repository/DocumentationRepositoryImplTest.kt b/data/src/test/java/com/androidvip/sysctlgui/data/repository/DocumentationRepositoryImplTest.kt new file mode 100644 index 0000000..c65dd3e --- /dev/null +++ b/data/src/test/java/com/androidvip/sysctlgui/data/repository/DocumentationRepositoryImplTest.kt @@ -0,0 +1,117 @@ +package com.androidvip.sysctlgui.data.repository + +import com.androidvip.sysctlgui.data.source.DocumentationDataSource +import com.androidvip.sysctlgui.domain.models.KernelParam +import com.androidvip.sysctlgui.domain.models.ParamDocumentation +import io.mockk.called +import io.mockk.coEvery +import io.mockk.coVerify +import io.mockk.mockk +import io.mockk.verify +import kotlinx.coroutines.delay +import kotlinx.coroutines.test.runTest +import org.junit.Assert +import org.junit.Before +import org.junit.Test + +class DocumentationRepositoryImplTest { + private val offlineDataSource: DocumentationDataSource = mockk(relaxed = true) + private val onlineDataSource: DocumentationDataSource = mockk(relaxed = true) + private lateinit var repository: DocumentationRepositoryImpl + + private val testParam = KernelParam( + name = "vm.swappiness", + path = "/proc/sys/vm/swappiness", + value = "100" + ) + + private val expectedDocumentation = ParamDocumentation( + title = "Swappiness", + documentationText = "Controls swappiness", + url = "https://docs.kernel.org/admin-guide/sysctl/vm.html#swappiness" + ) + + @Before + fun setUp() { + repository = DocumentationRepositoryImpl(offlineDataSource, onlineDataSource) + } + + @Test + fun `getDocumentation when online is true and online source succeeds then return online documentation`() = + runTest { + // Given + coEvery { onlineDataSource.getDocumentation(testParam) } returns expectedDocumentation + + // When + val result = repository.getDocumentation(testParam, online = true) + + // Then + coVerify(exactly = 1) { onlineDataSource.getDocumentation(testParam) } + verify { offlineDataSource wasNot called } + + Assert.assertEquals(expectedDocumentation, result) + } + + @Test + fun `getDocumentation when online is true and online source times out then return offline documentation`() = runTest { + // Given + coEvery { onlineDataSource.getDocumentation(testParam) } coAnswers { + delay(DocumentationRepositoryImpl.REQUEST_TIMEOUT_MS + 100) + null + } + + coEvery { onlineDataSource.getDocumentation(testParam) } returns null + coEvery { offlineDataSource.getDocumentation(testParam) } returns expectedDocumentation + + // When + val result = repository.getDocumentation(testParam, online = true) + + // Then + coVerify(exactly = 1) { onlineDataSource.getDocumentation(testParam) } + coVerify(exactly = 1) { offlineDataSource.getDocumentation(testParam) } + Assert.assertEquals(expectedDocumentation, result) + } + + @Test + fun `getDocumentation when online is false then return offline documentation`() = runTest { + // Given + coEvery { offlineDataSource.getDocumentation(testParam) } returns expectedDocumentation + + // When + val result = repository.getDocumentation(testParam, online = false) + + // Then + coVerify(exactly = 1) { offlineDataSource.getDocumentation(testParam) } + verify { onlineDataSource wasNot called } + Assert.assertEquals(expectedDocumentation, result) + } + + @Test + fun `getDocumentation when online is true and both sources return null then return null`() = runTest { + // Given + coEvery { onlineDataSource.getDocumentation(testParam) } returns null + coEvery { offlineDataSource.getDocumentation(testParam) } returns null + + // When + val result = repository.getDocumentation(testParam, online = true) + + // Then + coVerify(exactly = 1) { onlineDataSource.getDocumentation(testParam) } + coVerify(exactly = 1) { offlineDataSource.getDocumentation(testParam) } + Assert.assertNull(result) + } + + @Test + fun `getDocumentation when online is false and offline source returns null then return null`() = runTest { + // Given + coEvery { offlineDataSource.getDocumentation(testParam) } returns null + + // When + val result = repository.getDocumentation(testParam, online = false) + + // Then + coVerify(exactly = 1) { offlineDataSource.getDocumentation(testParam) } + verify { onlineDataSource wasNot called } + Assert.assertNull(result) + } +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index b18e73f..a7e7144 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -17,6 +17,7 @@ composeBom = "2025.07.00" appcompat = "1.7.1" materialIconsCoreCompose = "1.7.8" material = "1.12.0" +mockkAndroid = "1.14.5" multidex = "2.0.1" navigationCompose = "2.9.3" preference = "1.2.1" @@ -39,6 +40,7 @@ androidx-navigation-compose = { module = "androidx.navigation:navigation-compose androidx-preference = { module = "androidx.preference:preference", version.ref = "preference" } androidx-window = { module = "androidx.window:window", version.ref = "window" } androidx-work-runtime-ktx = { module = "androidx.work:work-runtime-ktx", version.ref = "workRuntimeKtx" } +kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "kotlinxCoroutinesAndroid" } libsu-core = { module = "com.github.topjohnwu.libsu:core", version.ref = "libsu" } libsu-nio = { module = "com.github.topjohnwu.libsu:nio", version.ref = "libsu" } jsoup = { module = "org.jsoup:jsoup", version.ref = "jsoup" } @@ -73,6 +75,7 @@ ktor-client-android = { module = "io.ktor:ktor-client-android", version.ref = "k ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" } ktor-client-logging = { module = "io.ktor:ktor-client-logging", version.ref = "ktor" } material = { module = "com.google.android.material:material", version.ref = "material" } +mockk-android = { module = "io.mockk:mockk-android", version.ref = "mockkAndroid" } [bundles] ktor-clients = ["ktor-client-core", "ktor-client-android", "ktor-client-logging"]