From fde5b1ebf052d8db585ee3c12a30f4f64f6a8172 Mon Sep 17 00:00:00 2001 From: Anselmo Alexandre Date: Thu, 27 Oct 2022 22:21:36 +0200 Subject: [PATCH 1/5] AuthManagerTest code improvements --- data/build.gradle.kts | 3 + .../android254/data/repos/AuthManagerTest.kt | 98 +++++++++++-------- 2 files changed, 60 insertions(+), 41 deletions(-) diff --git a/data/build.gradle.kts b/data/build.gradle.kts index f93cb7a..54947df 100644 --- a/data/build.gradle.kts +++ b/data/build.gradle.kts @@ -84,6 +84,9 @@ kotlin { languageSettings.apply { optIn("kotlinx.coroutines.ExperimentalCoroutinesApi") } + dependencies { + implementation("com.google.truth:truth:1.0.1") + } } } } \ No newline at end of file diff --git a/data/src/test/java/com/android254/data/repos/AuthManagerTest.kt b/data/src/test/java/com/android254/data/repos/AuthManagerTest.kt index b6a1a4a..1aca2c8 100644 --- a/data/src/test/java/com/android254/data/repos/AuthManagerTest.kt +++ b/data/src/test/java/com/android254/data/repos/AuthManagerTest.kt @@ -22,12 +22,12 @@ import com.android254.data.network.util.NetworkError import com.android254.data.network.util.TokenProvider import com.android254.domain.models.DataResult import com.android254.domain.models.Success +import com.google.common.truth.Truth import io.mockk.* -import kotlinx.coroutines.runBlocking -import org.hamcrest.CoreMatchers.`is` -import org.hamcrest.MatcherAssert.assertThat +import io.mockk.impl.annotations.MockK +import kotlinx.coroutines.test.runTest +import org.junit.Before import org.junit.Test -import java.lang.Exception class AuthManagerTest { private val fakeUserDetails = UserDetails( @@ -37,49 +37,65 @@ class AuthManagerTest { avatar = "http://test.com" ) + @MockK + private lateinit var authApi: AuthApi + + @MockK + private lateinit var tokenProvider: TokenProvider + + private lateinit var authManager: AuthManager + private lateinit var exception: Exception + private lateinit var networkError: NetworkError + + @Before + fun setup() { + MockKAnnotations.init(this, relaxUnitFun = true) + authManager = AuthManager(authApi, tokenProvider) + networkError = NetworkError() + exception = Exception() + } + @Test - fun `test getAndSaveApiToken successfully`() { - val mockApi = mockk() - val mockTokenProvider = mockk() - - runBlocking { - val repo = AuthManager(mockApi, mockTokenProvider) - coEvery { mockApi.googleLogin(any()) } returns AccessToken("test", user = fakeUserDetails) - coEvery { mockTokenProvider.update(any()) } just Runs - - val result = repo.getAndSaveApiToken("test") - assertThat(result, `is`(DataResult.Success(Success))) - coVerify { mockTokenProvider.update("test") } - } + fun `test getAndSaveApiToken successfully`() = runTest { + // Given + coEvery { authApi.googleLogin(any()) } returns AccessToken( + "test", + user = fakeUserDetails + ) + coEvery { tokenProvider.update(any()) } just Runs + + // When + val result = authManager.getAndSaveApiToken("test") + + //Then + coVerify { tokenProvider.update("test") } + + // And + Truth.assertThat(result).isEqualTo(DataResult.Success(Success)) } @Test - fun `test getAndSaveApiToken failure - network error`() { - val mockApi = mockk() - val mockTokenProvider = mockk() - - runBlocking { - val repo = AuthManager(mockApi, mockTokenProvider) - val exc = NetworkError() - - coEvery { mockApi.googleLogin(any()) } throws exc - val result = repo.getAndSaveApiToken("test") - assertThat(result, `is`(DataResult.Error("Login failed", true, exc))) - } + fun `test getAndSaveApiToken failure - network error`() = runTest { + // Given + val exc = NetworkError() + coEvery { authApi.googleLogin(any()) } throws networkError + + // When + val result = authManager.getAndSaveApiToken("test") + + // Then + Truth.assertThat(result).isEqualTo(DataResult.Error("Login failed", true, networkError)) } @Test - fun `test getAndSaveApiToken failure - other error`() { - val mockApi = mockk() - val mockTokenProvider = mockk() - - runBlocking { - val repo = AuthManager(mockApi, mockTokenProvider) - val exc = Exception() - - coEvery { mockApi.googleLogin(any()) } throws exc - val result = repo.getAndSaveApiToken("test") - assertThat(result, `is`(DataResult.Error("Login failed", exc = exc))) - } + fun `test getAndSaveApiToken failure - other error`() = runTest { + // Given + coEvery { authApi.googleLogin(any()) } throws exception + + // When + val result = authManager.getAndSaveApiToken("test") + + // Then + Truth.assertThat(result).isEqualTo(DataResult.Error("Login failed", exc = exception)) } } \ No newline at end of file From f18d3f6f4a527a6647202b8e8c6baf3a00682a44 Mon Sep 17 00:00:00 2001 From: Anselmo Alexandre Date: Thu, 27 Oct 2022 23:31:28 +0200 Subject: [PATCH 2/5] Refactoring AuthApiTest test file --- .../android254/data/network/AuthApiTest.kt | 50 +++++++++++-------- 1 file changed, 30 insertions(+), 20 deletions(-) diff --git a/data/src/test/java/com/android254/data/network/AuthApiTest.kt b/data/src/test/java/com/android254/data/network/AuthApiTest.kt index fb685e2..a773be6 100644 --- a/data/src/test/java/com/android254/data/network/AuthApiTest.kt +++ b/data/src/test/java/com/android254/data/network/AuthApiTest.kt @@ -29,12 +29,11 @@ import com.android254.data.network.models.responses.UserDetails import com.android254.data.network.util.HttpClientFactory import com.android254.data.network.util.ServerError import com.android254.data.preferences.DefaultTokenProvider +import com.google.common.truth.Truth import io.ktor.client.engine.mock.* import io.ktor.http.* import kotlinx.coroutines.delay -import kotlinx.coroutines.runBlocking -import org.hamcrest.CoreMatchers.`is` -import org.hamcrest.MatcherAssert.assertThat +import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -55,19 +54,21 @@ class AuthApiTest { @Test(expected = ServerError::class) fun `test ServerError is thrown when a server exception occurs`() { + // Given val mockEngine = MockEngine { delay(500) respondError(HttpStatusCode.InternalServerError) } val httpClient = HttpClientFactory(DefaultTokenProvider(testDataStore)).create(mockEngine) val api = AuthApi(httpClient) - runBlocking { - api.logout() - } + + // When + runTest { api.logout() } } @Test fun `test successful logout`() { + // Given val mockEngine = MockEngine { respond( content = """{"message": "Success"}""", @@ -77,14 +78,18 @@ class AuthApiTest { } val httpClient = HttpClientFactory(DefaultTokenProvider(testDataStore)).create(mockEngine) val api = AuthApi(httpClient) - runBlocking { - val response = api.logout() - assertThat(response, `is`(Status("Success"))) + + // Then + runTest { + api.logout().also { + Truth.assertThat(it).isEqualTo(Status("Success")) + } } } @Test fun `test successful google login`() { + // Given val content = """ { "token": "test", @@ -106,18 +111,23 @@ class AuthApiTest { } val httpClient = HttpClientFactory(DefaultTokenProvider(testDataStore)).create(mockEngine) val api = AuthApi(httpClient) - runBlocking { - val accessToken = AccessToken( - token = "test", - user = UserDetails( - name = "Magak Emmanuel", - email = "emashmagak@gmail.com", - gender = null, - avatar = "http://localhost:8000/upload/avatar/img-20181016-wa0026jpg.jpg" - ) + + val accessToken = AccessToken( + token = "test", + user = UserDetails( + name = "Magak Emmanuel", + email = "emashmagak@gmail.com", + gender = null, + avatar = "http://localhost:8000/upload/avatar/img-20181016-wa0026jpg.jpg" ) - val response = api.googleLogin(GoogleToken("some token")) - assertThat(response, `is`(accessToken)) + ) + + // Then + runTest { + api.googleLogin(GoogleToken("some token")).also { + Truth.assertThat(it).isEqualTo(accessToken) + } } + } } \ No newline at end of file From c60b4010f458f7856e4164d59285545777e166f9 Mon Sep 17 00:00:00 2001 From: Anselmo Alexandre Date: Fri, 28 Oct 2022 08:01:35 +0200 Subject: [PATCH 3/5] Tests improvements on SessionDaoTest file --- .../com/android254/data/dao/SessionDao.kt | 2 +- .../com/android254/data/dao/SessionDaoTest.kt | 49 ++++++++----------- 2 files changed, 22 insertions(+), 29 deletions(-) diff --git a/data/src/main/java/com/android254/data/dao/SessionDao.kt b/data/src/main/java/com/android254/data/dao/SessionDao.kt index f1ed1e2..b153585 100644 --- a/data/src/main/java/com/android254/data/dao/SessionDao.kt +++ b/data/src/main/java/com/android254/data/dao/SessionDao.kt @@ -23,5 +23,5 @@ import com.android254.data.db.model.Session interface SessionDao : BaseDao { @Query("SELECT * FROM SESSION") - fun fetchSessions(): List + suspend fun fetchSessions(): List // This should be suspended or return FLow [Note: Don't suspend Flows] } \ No newline at end of file diff --git a/data/src/test/java/com/android254/data/dao/SessionDaoTest.kt b/data/src/test/java/com/android254/data/dao/SessionDaoTest.kt index b6c0f1c..35118aa 100644 --- a/data/src/test/java/com/android254/data/dao/SessionDaoTest.kt +++ b/data/src/test/java/com/android254/data/dao/SessionDaoTest.kt @@ -15,51 +15,44 @@ */ package com.android254.data.dao -import android.content.Context -import androidx.room.Room -import androidx.test.core.app.ApplicationProvider -import com.android254.data.db.Database import com.android254.data.db.model.Session +import com.google.common.truth.Truth +import io.mockk.* +import io.mockk.impl.annotations.MockK import kotlinx.coroutines.test.runTest import kotlinx.datetime.toInstant -import org.hamcrest.CoreMatchers.`is` -import org.hamcrest.MatcherAssert.assertThat -import org.junit.After import org.junit.Before import org.junit.Test -import org.junit.runner.RunWith -import org.robolectric.RobolectricTestRunner -import java.io.IOException -@RunWith(RobolectricTestRunner::class) class SessionDaoTest { - + private lateinit var session: Session + @MockK private lateinit var sessionDao: SessionDao - private lateinit var db: Database @Before fun setup() { - val context = ApplicationProvider.getApplicationContext() - db = Room.inMemoryDatabaseBuilder( - context, - Database::class.java + MockKAnnotations.init(this, relaxUnitFun = true) + session = Session( + id = 0, + name = "Welcome Keynote", + publishDate = "2010-06-01T22:19:44.475Z".toInstant() ) - .allowMainThreadQueries() // TODO Please delete me - .build() - sessionDao = db.sessionDao() - } - @After - @Throws(IOException::class) - fun tearDown() { - db.close() + coJustRun { sessionDao.insert(session) } + coEvery { sessionDao.fetchSessions() } returns listOf(session) } @Test fun `test sessionDao fetches all sessions`() = runTest { - val session = Session(0, "Welcome Keynote", "2010-06-01T22:19:44.475Z".toInstant()) + // Given sessionDao.insert(session) - val result = sessionDao.fetchSessions().first() - assertThat(session.name, `is`(result.name)) + + // When + val result = sessionDao.fetchSessions().first().name + + // Then + coVerify(atLeast = 1) { sessionDao.insert(session) } + coVerify { sessionDao.insert(session) } + Truth.assertThat(result).isEqualTo(session.name) } } \ No newline at end of file From 0149805630c55e27464448968142e68c67ec7f82 Mon Sep 17 00:00:00 2001 From: Anselmo Alexandre Date: Fri, 28 Oct 2022 08:03:30 +0200 Subject: [PATCH 4/5] Few suggestions --- .../main/java/com/android254/data/network/util/TokenProvider.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/src/main/java/com/android254/data/network/util/TokenProvider.kt b/data/src/main/java/com/android254/data/network/util/TokenProvider.kt index f5dac86..afb5adf 100644 --- a/data/src/main/java/com/android254/data/network/util/TokenProvider.kt +++ b/data/src/main/java/com/android254/data/network/util/TokenProvider.kt @@ -19,7 +19,7 @@ import kotlinx.coroutines.flow.Flow interface TokenProvider { - suspend fun fetch(): Flow + suspend fun fetch(): Flow // @Allan -> Don't suspend Flows suspend fun update(accessToken: String) } \ No newline at end of file From 94808256743cd2729700f25d8a72af80bbd8b9e8 Mon Sep 17 00:00:00 2001 From: Anselmo Alexandre Date: Fri, 28 Oct 2022 10:10:22 +0200 Subject: [PATCH 5/5] Adding Truth library dependency to versions catalog --- data/build.gradle.kts | 4 +--- gradle/libs.versions.toml | 1 + 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/data/build.gradle.kts b/data/build.gradle.kts index 54947df..b29c7f4 100644 --- a/data/build.gradle.kts +++ b/data/build.gradle.kts @@ -76,6 +76,7 @@ dependencies { testImplementation(libs.test.robolectric) testImplementation(libs.ktor.mock) testImplementation(libs.test.mockk) + testImplementation(libs.google.truth) } kotlin { @@ -84,9 +85,6 @@ kotlin { languageSettings.apply { optIn("kotlinx.coroutines.ExperimentalCoroutinesApi") } - dependencies { - implementation("com.google.truth:truth:1.0.1") - } } } } \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index bdbbf9d..d48aa3e 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -75,6 +75,7 @@ firebase-messaging = { module = "com.google.firebase:firebase-messaging-ktx" } firebase-crashlytics = { module = "com.google.firebase:firebase-crashlytics-ktx" } firebase-analytics = { module = "com.google.firebase:firebase-analytics-ktx" } firebase-performance = { module = "com.google.firebase:firebase-perf-ktx" } +google-truth = "com.google.truth:truth:1.0.1" [bundles] compose = ["coil-compose", "compose-activity", "compose-compiler", "compose-material-3", "compose-materialIcons", "compose-runtimeLivedata", "compose-ui", "compose-ui-tooling", "compose-ui-tooling-preview", "paging-compose", "compose-preview-customview", "compose-preview-customview-poolingcontainer", "compose-constraintlayout"]