diff --git a/data/build.gradle.kts b/data/build.gradle.kts index 3d79fdd..8fe3a63 100644 --- a/data/build.gradle.kts +++ b/data/build.gradle.kts @@ -77,6 +77,7 @@ dependencies { testImplementation(libs.test.robolectric) testImplementation(libs.ktor.mock) testImplementation(libs.test.mockk) + testImplementation(libs.google.truth) } kotlin { 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 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 f08d50d..79c9c0e 100644 --- a/data/src/test/java/com/android254/data/dao/SessionDaoTest.kt +++ b/data/src/test/java/com/android254/data/dao/SessionDaoTest.kt @@ -15,50 +15,24 @@ */ 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 kotlinx.coroutines.flow.first -import kotlinx.coroutines.runBlocking +import com.google.common.truth.Truth +import io.mockk.* +import io.mockk.impl.annotations.MockK +import kotlinx.coroutines.flow.* import kotlinx.coroutines.test.runTest -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 - ) - .allowMainThreadQueries() // TODO Please delete me - .build() - sessionDao = db.sessionDao() - } - - @After - @Throws(IOException::class) - fun tearDown() { - db.close() - } - - @Test - fun `test sessionDao fetches all sessions`() = runTest { - val session = Session( + MockKAnnotations.init(this, relaxUnitFun = true) + session = Session( id = 0, title = "Retrofiti: A Pragmatic Approach to using Retrofit in Android", description = "This session is codelab covering some of the best practices and recommended approaches to building an application using the retrofit library.", @@ -66,10 +40,22 @@ class SessionDaoTest { session_format = "Codelab / Workshop", session_level = "Intermediate", ) + + coJustRun { sessionDao.insert(session) } + coEvery { sessionDao.fetchSessions() } returns flow { emit(listOf(session)) } + } + + @Test + fun `test sessionDao fetches all sessions`() = runTest { + // Given sessionDao.insert(session) - runBlocking { - val result = sessionDao.fetchSessions().first().first() - assertThat(session.title, `is`(result.title)) - } + + // When + val result = sessionDao.fetchSessions().first() + + // Then + coVerify(atLeast = 1) { sessionDao.insert(session) } + coVerify { sessionDao.insert(session) } + Truth.assertThat(result.first().title).isEqualTo(session.title) } } \ No newline at end of file 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 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 diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 80dc4c6..6039c2f 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -76,6 +76,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"]