diff --git a/app/src/androidTest/java/com/juandgaines/testground/ExampleInstrumentedTest.kt b/app/src/androidTest/java/com/juandgaines/testground/ExampleInstrumentedTest.kt index 142450a..ed08157 100644 --- a/app/src/androidTest/java/com/juandgaines/testground/ExampleInstrumentedTest.kt +++ b/app/src/androidTest/java/com/juandgaines/testground/ExampleInstrumentedTest.kt @@ -20,5 +20,6 @@ class ExampleInstrumentedTest { // Context of the app under test. val appContext = InstrumentationRegistry.getInstrumentation().targetContext assertEquals("com.juandgaines.testground", appContext.packageName) + } } \ 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 deleted file mode 100644 index 5a0bdd9..0000000 --- a/app/src/test/java/com/juandgaines/testground/data/UserRepositoryTest.kt +++ /dev/null @@ -1,195 +0,0 @@ -package com.juandgaines.testground.data - -import com.google.common.truth.Truth.assertThat -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.Profile -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 -import java.util.UUID - -@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 - assertThat(profileResult.isSuccess).isTrue() - assertThat(profileResult.getOrThrow().user.id).isEqualTo("1") - - val expectedPlaces = api.places.filter { it.id == "1" } - 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 - assertThat(result.isSuccess).isTrue() - assertThat(result.getOrThrow().user).isEqualTo(user) - 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 - assertThat(result.isFailure).isTrue() - assertThat(result.exceptionOrNull()).isInstanceOf(HttpException::class.java) - } -} - -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/domain/ExperienceCalculatorTest.kt b/app/src/test/java/com/juandgaines/testground/domain/ExperienceCalculatorTest.kt new file mode 100644 index 0000000..db4bd8f --- /dev/null +++ b/app/src/test/java/com/juandgaines/testground/domain/ExperienceCalculatorTest.kt @@ -0,0 +1,97 @@ +package com.juandgaines.testground.domain + +import com.google.common.truth.Truth +import org.junit.Before +import org.junit.Test + + +class ExperienceCalculatorTest { + + private lateinit var experienceCalculator: ExperienceCalculator + + @Before + fun setup() { + experienceCalculator = ExperienceCalculator() + } + + @Test + fun givenTouristSpot_whenCalculateExperience_thenReturns5Points() { + // Arrange + val touristSpot = Place( + id = "1", + name = "Times Square", + coordinates = Coordinates(40.5, -73.5) + ) + + // Act + val result = experienceCalculator.calculateExperience(listOf(touristSpot)) + + // Assert + Truth.assertThat(result).isEqualTo(5) + } + + @Test + fun givenCulturalPlace_whenCalculateExperience_thenReturns4Points() { + // Arrange + val culturalPlace = Place( + id = "2", + name = "Smithsonian Museum", + coordinates = Coordinates(38.5, -76.5) + ) + + // Act + val result = experienceCalculator.calculateExperience(listOf(culturalPlace)) + + // Assert + Truth.assertThat(result).isEqualTo(4) + } + + @Test + fun givenMultiplePlaces_whenCalculateExperience_thenReturnsSumOfScores() { + // Arrange + val places = listOf( + Place("1", "Tourist Spot", Coordinates(40.5, -73.5)), // 5 points + Place("2", "Cultural Place", Coordinates(38.5, -76.5)), // 4 points + Place("3", "Unknown Place", Coordinates(0.0, 0.0)) // 1 point + ) + + // Act + val result = experienceCalculator.calculateExperience(places) + + // Assert + Truth.assertThat(result).isEqualTo(10) + } + + @Test + fun givenEmptyList_whenCalculateExperience_thenReturnsZero() { + // Act + val result = experienceCalculator.calculateExperience(emptyList()) + + // Assert + Truth.assertThat(result).isEqualTo(0) + } + + /** + * Test naming convention explanation: + * + * Format: `given[Condition]_when[Action]_then[ExpectedResult]` + * + * Why this format? + * 1. Uses backticks to allow spaces and natural language + * 2. Clearly separates test conditions, actions, and expected results + * 3. Follows BDD (Behavior-Driven Development) principles + * 4. Makes test purpose immediately clear + * + * Truth Assertions advantages: + * 1. More readable than JUnit assertions + * 2. Better error messages + * 3. Fluent API with clear intent + * 4. Chain-able assertions + * + * Example assertions: + * assertThat(value).isEqualTo(expected) + * assertThat(list).hasSize(expected) + * assertThat(string).contains(substring) + * assertThat(boolean).isTrue() + */ +} \ No newline at end of file