From 8b0a5e95d042702f1353d47e3cab6bd77c68052e Mon Sep 17 00:00:00 2001 From: Juan Gaines Date: Tue, 8 Apr 2025 08:04:08 -0500 Subject: [PATCH 1/3] test doubles --- .../testground/data/UserApiFake.kt | 37 ++++ .../testground/data/UserRepositoryTest.kt | 163 ++++++++++++++++++ 2 files changed, 200 insertions(+) create mode 100644 app/src/test/java/com/juandgaines/testground/data/UserApiFake.kt create mode 100644 app/src/test/java/com/juandgaines/testground/data/UserRepositoryTest.kt diff --git a/app/src/test/java/com/juandgaines/testground/data/UserApiFake.kt b/app/src/test/java/com/juandgaines/testground/data/UserApiFake.kt new file mode 100644 index 0000000..ceb7b87 --- /dev/null +++ b/app/src/test/java/com/juandgaines/testground/data/UserApiFake.kt @@ -0,0 +1,37 @@ +package com.juandgaines.testground.data + +import com.juandgaines.testground.domain.Coordinates +import com.juandgaines.testground.domain.Place +import com.juandgaines.testground.domain.Profile +import com.juandgaines.testground.domain.User +import java.util.UUID + +class UserApiFake : UserApi { + var users = (1..10).map { + User( + id = it.toString(), + username = "User$it" + ) + } + + var places = (1..10).map { + Place( + id = UUID.randomUUID().toString(), + name = "Place$it", + coordinates = Coordinates(it.toDouble(), it.toDouble()) + ) + } + + override suspend fun getUser(userId: String): User { + return users.find { it.id == userId } ?: throw Exception("User not found") + } + + override suspend fun getPlaces(userId: String): List { + return places.filter { it.id == userId } + } + + override suspend fun getProfile(userId: String): Profile { + val user = getUser(userId) + return Profile(user = user, places = getPlaces(userId)) + } +} \ No newline at end of file diff --git a/app/src/test/java/com/juandgaines/testground/data/UserRepositoryTest.kt b/app/src/test/java/com/juandgaines/testground/data/UserRepositoryTest.kt new file mode 100644 index 0000000..710752a --- /dev/null +++ b/app/src/test/java/com/juandgaines/testground/data/UserRepositoryTest.kt @@ -0,0 +1,163 @@ +package com.juandgaines.testground.data + +import com.google.common.truth.Truth +import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory +import com.juandgaines.testground.domain.Coordinates +import com.juandgaines.testground.domain.Place +import com.juandgaines.testground.domain.User +import com.juandgaines.testground.util.MainDispatcherRule +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest +import kotlinx.serialization.json.Json +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.mockwebserver.MockResponse +import okhttp3.mockwebserver.MockWebServer +import org.junit.After +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import retrofit2.HttpException +import retrofit2.Retrofit + +@OptIn(ExperimentalCoroutinesApi::class) +class UserRepositoryTest { + + @get:Rule + val mainDispatcherRule = MainDispatcherRule() + + private lateinit var repository: UserRepositoryImpl + private lateinit var api: UserApiFake + private lateinit var mockWebServer: MockWebServer + private lateinit var mockApi: UserApi + + @Before + fun setUp() { + // Setup for fake API tests + api = UserApiFake() + repository = UserRepositoryImpl(api) + + // Setup for MockWebServer tests + val contentType = "application/json".toMediaType() + mockWebServer = MockWebServer() + mockApi = Retrofit.Builder() + .baseUrl(mockWebServer.url("/")) + .addConverterFactory(Json.asConverterFactory(contentType)) + .build() + .create(UserApi::class.java) + } + + @After + fun tearDown() { + mockWebServer.shutdown() + } + + @Test + fun givenValidUserId_whenGetProfileWithFakeApi_thenReturnsProfile() = runTest { + // Act + val profileResult = repository.getProfile("1") + + // Assert + Truth.assertThat(profileResult.isSuccess).isTrue() + Truth.assertThat(profileResult.getOrThrow().user.id).isEqualTo("1") + + val expectedPlaces = api.places.filter { it.id == "1" } + Truth.assertThat(profileResult.getOrThrow().places).isEqualTo(expectedPlaces) + } + + @Test + fun givenValidUserId_whenGetProfileWithMockWebServer_thenReturnsProfile() = runTest { + // Arrange + val user = User(id = "1", username = "test-user") + val places = listOf( + Place( + id = "1", + name = "Test Place 1", + coordinates = Coordinates(latitude = 1.0, longitude = 1.0) + ), + Place( + id = "2", + name = "Test Place 2", + coordinates = Coordinates(latitude = 2.0, longitude = 2.0) + ) + ) + + // Prepare mock responses with proper JSON structure + mockWebServer.enqueue( + MockResponse() + .setResponseCode(200) + .setBody(""" + { + "id": "1", + "username": "test-user" + } + """.trimIndent()) + ) + mockWebServer.enqueue( + MockResponse() + .setResponseCode(200) + .setBody(""" + [ + { + "id": "1", + "name": "Test Place 1", + "coordinates": { + "latitude": 1.0, + "longitude": 1.0 + } + }, + { + "id": "2", + "name": "Test Place 2", + "coordinates": { + "latitude": 2.0, + "longitude": 2.0 + } + } + ] + """.trimIndent()) + ) + + // Act + val repository = UserRepositoryImpl(mockApi) + val result = repository.getProfile("1") + + // Assert + Truth.assertThat(result.isSuccess).isTrue() + Truth.assertThat(result.getOrThrow().user).isEqualTo(user) + Truth.assertThat(result.getOrThrow().places).isEqualTo(places) + } + + @Test + fun givenInvalidUserId_whenGetProfile_thenReturnsError() = runTest { + // Arrange + mockWebServer.enqueue( + MockResponse() + .setResponseCode(404) + .setBody(""" + { + "error": "User not found", + "status": 404 + } + """.trimIndent()) + ) + + mockWebServer.enqueue( + MockResponse() + .setResponseCode(404) + .setBody(""" + { + "error": "User not found", + "status": 404 + } + """.trimIndent()) + ) + + // Act + val repository = UserRepositoryImpl(mockApi) + val result = repository.getProfile("invalid-id") + + // Assert + Truth.assertThat(result.isFailure).isTrue() + Truth.assertThat(result.exceptionOrNull()).isInstanceOf(HttpException::class.java) + } +} \ No newline at end of file From d3fb2de92ce3c35a51d50e0e4b1d242235ca1671 Mon Sep 17 00:00:00 2001 From: Juan Gaines Date: Wed, 9 Apr 2025 14:36:35 -0500 Subject: [PATCH 2/3] navigation tests --- .../testground/data/UserRepositoryTest.kt | 111 ------------------ 1 file changed, 111 deletions(-) diff --git a/app/src/test/java/com/juandgaines/testground/data/UserRepositoryTest.kt b/app/src/test/java/com/juandgaines/testground/data/UserRepositoryTest.kt index 710752a..d14ef46 100644 --- a/app/src/test/java/com/juandgaines/testground/data/UserRepositoryTest.kt +++ b/app/src/test/java/com/juandgaines/testground/data/UserRepositoryTest.kt @@ -27,29 +27,14 @@ class UserRepositoryTest { private lateinit var repository: UserRepositoryImpl private lateinit var api: UserApiFake - private lateinit var mockWebServer: MockWebServer - private lateinit var mockApi: UserApi @Before fun setUp() { // Setup for fake API tests api = UserApiFake() repository = UserRepositoryImpl(api) - - // Setup for MockWebServer tests - val contentType = "application/json".toMediaType() - mockWebServer = MockWebServer() - mockApi = Retrofit.Builder() - .baseUrl(mockWebServer.url("/")) - .addConverterFactory(Json.asConverterFactory(contentType)) - .build() - .create(UserApi::class.java) } - @After - fun tearDown() { - mockWebServer.shutdown() - } @Test fun givenValidUserId_whenGetProfileWithFakeApi_thenReturnsProfile() = runTest { @@ -64,100 +49,4 @@ class UserRepositoryTest { Truth.assertThat(profileResult.getOrThrow().places).isEqualTo(expectedPlaces) } - @Test - fun givenValidUserId_whenGetProfileWithMockWebServer_thenReturnsProfile() = runTest { - // Arrange - val user = User(id = "1", username = "test-user") - val places = listOf( - Place( - id = "1", - name = "Test Place 1", - coordinates = Coordinates(latitude = 1.0, longitude = 1.0) - ), - Place( - id = "2", - name = "Test Place 2", - coordinates = Coordinates(latitude = 2.0, longitude = 2.0) - ) - ) - - // Prepare mock responses with proper JSON structure - mockWebServer.enqueue( - MockResponse() - .setResponseCode(200) - .setBody(""" - { - "id": "1", - "username": "test-user" - } - """.trimIndent()) - ) - mockWebServer.enqueue( - MockResponse() - .setResponseCode(200) - .setBody(""" - [ - { - "id": "1", - "name": "Test Place 1", - "coordinates": { - "latitude": 1.0, - "longitude": 1.0 - } - }, - { - "id": "2", - "name": "Test Place 2", - "coordinates": { - "latitude": 2.0, - "longitude": 2.0 - } - } - ] - """.trimIndent()) - ) - - // Act - val repository = UserRepositoryImpl(mockApi) - val result = repository.getProfile("1") - - // Assert - Truth.assertThat(result.isSuccess).isTrue() - Truth.assertThat(result.getOrThrow().user).isEqualTo(user) - Truth.assertThat(result.getOrThrow().places).isEqualTo(places) - } - - @Test - fun givenInvalidUserId_whenGetProfile_thenReturnsError() = runTest { - // Arrange - mockWebServer.enqueue( - MockResponse() - .setResponseCode(404) - .setBody(""" - { - "error": "User not found", - "status": 404 - } - """.trimIndent()) - ) - - mockWebServer.enqueue( - MockResponse() - .setResponseCode(404) - .setBody(""" - { - "error": "User not found", - "status": 404 - } - """.trimIndent()) - ) - - // Act - val repository = UserRepositoryImpl(mockApi) - val result = repository.getProfile("invalid-id") - - // Assert - Truth.assertThat(result.isFailure).isTrue() - Truth.assertThat(result.exceptionOrNull()).isInstanceOf(HttpException::class.java) - } } \ No newline at end of file From fe262d8cc9e56ec8f50661653dc90a3718379cf1 Mon Sep 17 00:00:00 2001 From: Juan Gaines Date: Wed, 9 Apr 2025 14:37:16 -0500 Subject: [PATCH 3/3] navigation tests --- .../juandgaines/testground/data/UserRepositoryTest.kt | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/app/src/test/java/com/juandgaines/testground/data/UserRepositoryTest.kt b/app/src/test/java/com/juandgaines/testground/data/UserRepositoryTest.kt index d14ef46..3c97e0b 100644 --- a/app/src/test/java/com/juandgaines/testground/data/UserRepositoryTest.kt +++ b/app/src/test/java/com/juandgaines/testground/data/UserRepositoryTest.kt @@ -1,23 +1,12 @@ package com.juandgaines.testground.data import com.google.common.truth.Truth -import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory -import com.juandgaines.testground.domain.Coordinates -import com.juandgaines.testground.domain.Place -import com.juandgaines.testground.domain.User import com.juandgaines.testground.util.MainDispatcherRule import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest -import kotlinx.serialization.json.Json -import okhttp3.MediaType.Companion.toMediaType -import okhttp3.mockwebserver.MockResponse -import okhttp3.mockwebserver.MockWebServer -import org.junit.After import org.junit.Before import org.junit.Rule import org.junit.Test -import retrofit2.HttpException -import retrofit2.Retrofit @OptIn(ExperimentalCoroutinesApi::class) class UserRepositoryTest {