Skip to content

Commit

Permalink
Add UT for LoadTimeTable
Browse files Browse the repository at this point in the history
  • Loading branch information
ksharma-xyz committed Jan 8, 2025
1 parent f382e4e commit 77d8448
Show file tree
Hide file tree
Showing 4 changed files with 222 additions and 6 deletions.
Original file line number Diff line number Diff line change
@@ -1,23 +1,47 @@
package xyz.ksharma.core.test.fakes

import xyz.ksharma.core.test.fakes.FakeTripResponseBuilder.buildDestinationStopSequence
import xyz.ksharma.core.test.fakes.FakeTripResponseBuilder.buildOriginStopSequence
import xyz.ksharma.core.test.fakes.FakeTripResponseBuilder.buildTransportation
import xyz.ksharma.krail.trip.planner.network.api.model.StopFinderResponse
import xyz.ksharma.krail.trip.planner.network.api.model.StopType
import xyz.ksharma.krail.trip.planner.network.api.model.TripResponse
import xyz.ksharma.krail.trip.planner.network.api.model.TripResponse.StopSequence
import xyz.ksharma.krail.trip.planner.network.api.service.DepArr
import xyz.ksharma.krail.trip.planner.network.api.service.TripPlanningService

class FakeTripPlanningService : TripPlanningService {

var isSuccess: Boolean = true

override suspend fun trip(
originStopId: String,
destinationStopId: String,
depArr: DepArr,
date: String?,
time: String?,
): TripResponse {
return if (isSuccess)

// Return a fake TripResponse
return TripResponse(
)
TripResponse(
journeys = listOf(
TripResponse.Journey(
legs = listOf(
TripResponse.Leg(
origin = buildOriginStopSequence(),
destination = buildDestinationStopSequence(),
stopSequence = listOf(
buildOriginStopSequence(),
),
transportation = buildTransportation(),
duration = 100,
),
),
),
)
)
else throw IllegalStateException("Failed to fetch trip")
}

override suspend fun stopFinder(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package xyz.ksharma.core.test.fakes

import xyz.ksharma.krail.trip.planner.network.api.model.StopType
import xyz.ksharma.krail.trip.planner.network.api.model.TripResponse
import xyz.ksharma.krail.trip.planner.network.api.model.TripResponse.StopSequence

object FakeTripResponseBuilder {

fun buildOriginStopSequence() = StopSequence(
arrivalTimePlanned = "2024-09-24T19:00:00Z",
arrivalTimeEstimated = "2024-09-24T19:10:00Z",
departureTimePlanned = "2024-09-24T19:10:00Z",
departureTimeEstimated = "2024-09-24T19:10:00Z",
name = "Origin Stop",
disassembledName = "Origin Name",
id = "Origin_stop_id",
type = StopType.STOP.type,
)

fun buildDestinationStopSequence() = StopSequence(
arrivalTimePlanned = "2024-09-24T20:00:00Z",
arrivalTimeEstimated = "2024-09-24T20:10:00Z",
departureTimePlanned = "2024-09-24T20:10:00Z",
departureTimeEstimated = "2024-09-24T20:10:00Z",
name = "Destination Stop",
disassembledName = "Destination Name",
id = "Destination_stop_id",
type = StopType.STOP.type,
)

fun buildTransportation() = TripResponse.Transportation(
disassembledName = "Transportation Name",
product = TripResponse.Product(
productClass = 1,
name = "Train",
),
destination = TripResponse.OperatorClass(
name = "Destination Operator",
id = "Destination Operator Id",
),
name = "Transportation Name",
id = "Transportation Id",
description = "Transportation Description",
)
}
Original file line number Diff line number Diff line change
@@ -1,19 +1,30 @@
package xyz.ksharma.core.test.viewmodels

import app.cash.turbine.test
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.advanceUntilIdle
import kotlinx.coroutines.test.resetMain
import kotlinx.coroutines.test.runTest
import kotlinx.coroutines.test.setMain
import xyz.ksharma.core.test.fakes.FakeAnalytics
import xyz.ksharma.core.test.fakes.FakeRateLimiter
import xyz.ksharma.core.test.fakes.FakeSandook
import xyz.ksharma.core.test.fakes.FakeTripPlanningService
import xyz.ksharma.krail.core.analytics.Analytics
import xyz.ksharma.krail.sandook.Sandook
import xyz.ksharma.krail.trip.planner.ui.state.timetable.TimeTableUiEvent
import xyz.ksharma.krail.trip.planner.ui.state.timetable.Trip
import xyz.ksharma.krail.trip.planner.ui.timetable.TimeTableViewModel
import kotlin.test.AfterTest
import kotlin.test.BeforeTest
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertIs
import kotlin.test.assertNull
import kotlin.test.assertTrue

@OptIn(ExperimentalCoroutinesApi::class)
class TimeTableViewModelTest {
Expand Down Expand Up @@ -42,4 +53,130 @@ class TimeTableViewModelTest {
fun tearDown() {
Dispatchers.resetMain()
}

@Test
fun `GIVEN initial state WHEN observer is active THEN fetchTrip and trackScreenViewEvent should be called`() =
runTest {
// Ensure analytics events have not been tracked before observation
assertFalse((analytics as FakeAnalytics).isEventTracked("view_screen"))

viewModel.isLoading.test {
val isLoadingState = awaitItem()
assertEquals(isLoadingState, true)

advanceUntilIdle()
assertTrue(analytics.isEventTracked("view_screen"))

cancelAndConsumeRemainingEvents()
}
}

@Test
fun `GIVEN a trip WHEN LoadTimeTable event is triggered and Trip API is success response THEN UI State must update with journeyList`() =
runTest {
// GIVEN a trip
val trip = Trip(
fromStopId = "FROM_STOP_ID_1",
fromStopName = "STOP_NAME_1",
toStopId = "TO_STOP_ID_1",
toStopName = "STOP_NAME_2"
)
tripPlanningService.isSuccess = true

// THEN verify that the UI state is updated correctly
viewModel.uiState.test {
val initialState = awaitItem()
initialState.run {
assertTrue(isLoading)
assertNull(initialState.trip)
assertFalse(isError)
assertFalse(isTripSaved)
}

// WHEN the LoadTimeTable event is triggered
viewModel.onEvent(TimeTableUiEvent.LoadTimeTable(trip))
viewModel.fetchTrip() // Manually call fetchTrip() to simulate the actual behavior
awaitItem().run {
assertTrue(isLoading)
assertFalse(silentLoading)
assertFalse(isError)
assertTrue(journeyList.isEmpty())
}

// need to skip two items, because silentLoading will be toggled, as we manually call fetchTrip()
skipItems(2)
/*
awaitItem().run {
assertTrue(silentLoading)
}
awaitItem().run {
assertFalse(silentLoading)
}
*/

awaitItem().run {
assertFalse(isLoading)
assertFalse(silentLoading)
assertTrue(journeyList.isNotEmpty())
assertEquals(expected = 1, journeyList.size)
}

cancelAndConsumeRemainingEvents()
}
}

@Test
fun `GIVEN a trip WHEN LoadTimeTable event is triggered and Trip API is error response THEN UIState should have isError as true`() =
runTest {
// GIVEN a trip
val trip = Trip(
fromStopId = "FROM_STOP_ID_1",
fromStopName = "STOP_NAME_1",
toStopId = "TO_STOP_ID_1",
toStopName = "STOP_NAME_2"
)
tripPlanningService.isSuccess = false

// THEN verify that the UI state is updated correctly
viewModel.uiState.test {
val initialState = awaitItem()
initialState.run {
assertTrue(isLoading)
assertNull(initialState.trip)
assertFalse(isError)
assertFalse(isTripSaved)
}

// WHEN the LoadTimeTable event is triggered
viewModel.onEvent(TimeTableUiEvent.LoadTimeTable(trip))
viewModel.fetchTrip() // Manually call fetchTrip() to simulate the actual behavior
awaitItem().run {
assertTrue(isLoading)
assertFalse(silentLoading)
assertFalse(isError)
assertTrue(journeyList.isEmpty())
}

// need to skip two items, because silentLoading will be toggled, as we manually call fetchTrip()
skipItems(2)
/*
awaitItem().run {
assertTrue(silentLoading)
}
awaitItem().run {
assertFalse(silentLoading)
}
*/

awaitItem().run {
assertFalse(isLoading)
assertFalse(silentLoading)
assertTrue(journeyList.isEmpty())
assertTrue(isError)
}

cancelAndConsumeRemainingEvents()
}
}

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package xyz.ksharma.krail.trip.planner.ui.timetable

import androidx.annotation.VisibleForTesting
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.collections.immutable.toImmutableList
Expand Down Expand Up @@ -169,7 +170,8 @@ class TimeTableViewModel(
}
}

private fun fetchTrip() {
@VisibleForTesting
fun fetchTrip() {
log("fetchTrip API Call")
fetchTripJob?.cancel()
updateUiState { copy(silentLoading = true) }
Expand All @@ -181,6 +183,7 @@ class TimeTableViewModel(
}.catch { e ->
log("Error while fetching trip: $e")
}.collectLatest { result ->
println("result Success: $result")
updateUiState { copy(silentLoading = false) }
result.onSuccess { response ->
updateTripsCache(response)
Expand Down Expand Up @@ -239,12 +242,17 @@ class TimeTableViewModel(
}

private fun updateUiStateWithFilteredTrips() {
println("updateUiStateWithFilteredTrips")

val journeyList = updateJourneyCardInfoTimeText(journeys.values.toList())
.sortedBy { it.originUtcDateTime.utcToLocalDateTimeAEST() }
.toImmutableList()

println("updateUiStateWithFilteredTrips: ${journeyList.size}")
updateUiState {
copy(
isLoading = false,
journeyList = updateJourneyCardInfoTimeText(journeys.values.toList())
.sortedBy { it.originUtcDateTime.utcToLocalDateTimeAEST() }
.toImmutableList(),
journeyList = journeyList,
isError = false,
)
}
Expand All @@ -268,6 +276,8 @@ class TimeTableViewModel(
else -> DepArr.DEP
}
)
println("tripResponse: $tripResponse")

Result.success(tripResponse)
}.getOrElse { error ->
Result.failure(error)
Expand Down

0 comments on commit 77d8448

Please sign in to comment.