Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package com.example.cryptoincompose.presentation.screens.home

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.example.cryptoincompose.core.Result
import com.example.cryptoincompose.domain.entities.Coin
import com.example.cryptoincompose.domain.usecase.GetTrendingCryptoUseCase
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import javax.inject.Inject

@HiltViewModel
class HomeViewModel @Inject constructor(
private val getTrendingCryptoUseCase: GetTrendingCryptoUseCase
) : ViewModel() {

private val _trendingCryptoState: MutableStateFlow<TrendingCryptoState> =
MutableStateFlow(TrendingCryptoState.Loading)

init {
fetchTrendingCrypto()
}

val trendingCryptoState = _trendingCryptoState.stateIn(
viewModelScope,
SharingStarted.Eagerly,
initialValue = TrendingCryptoState.Loading
)

private fun fetchTrendingCrypto() {
viewModelScope.launch {
setTrendingCryptoState(TrendingCryptoState.Loading)
val response = getTrendingCryptoUseCase.execute()
handleGetTrendingCryptoResponse(response)
}
}

private fun handleGetTrendingCryptoResponse(response: Result<List<Coin>>) {
when (response) {
is Result.Success -> setTrendingCryptoState(TrendingCryptoState.Success(response.data))
is Result.Error -> setTrendingCryptoState(TrendingCryptoState.Error(response.exception))
}
}

private fun setTrendingCryptoState(state: TrendingCryptoState) {
_trendingCryptoState.update {
state
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.example.cryptoincompose.presentation.screens.home

import com.example.cryptoincompose.domain.entities.Coin

sealed class TrendingCryptoState {
data object Loading : TrendingCryptoState()
data class Success(val listOfCrypto: List<Coin>) : TrendingCryptoState()
data class Error(val exception: Exception) : TrendingCryptoState()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.example.cryptoincompose.presentation.home

import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestDispatcher
import kotlinx.coroutines.test.setMain
import org.junit.rules.TestWatcher
import org.junit.runner.Description

@ExperimentalCoroutinesApi
class CoroutineTestRule constructor(
private val testDispatcher: TestDispatcher = StandardTestDispatcher()
) : TestWatcher() {

override fun starting(description: Description?) {
super.starting(description)
Dispatchers.setMain(testDispatcher)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package com.example.cryptoincompose.presentation.home

import com.example.cryptoincompose.core.Result
import com.example.cryptoincompose.domain.entities.Coin
import com.example.cryptoincompose.domain.usecase.GetTrendingCryptoUseCase
import com.example.cryptoincompose.presentation.screens.home.HomeViewModel
import com.example.cryptoincompose.presentation.screens.home.TrendingCryptoState
import io.mockk.MockKAnnotations
import io.mockk.coEvery
import io.mockk.impl.annotations.MockK
import junit.framework.TestCase.assertEquals
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.advanceUntilIdle
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import java.io.IOException

@OptIn(ExperimentalCoroutinesApi::class)
class HomeViewModelTest {

@ExperimentalCoroutinesApi
@get:Rule
val testCoroutineRule = CoroutineTestRule()

@MockK
private lateinit var getTrendingCryptoUseCase: GetTrendingCryptoUseCase

private lateinit var homeViewModel: HomeViewModel

@Before
fun setUp() {
MockKAnnotations.init(this)
homeViewModel = HomeViewModel(getTrendingCryptoUseCase)
}

@Test
fun `fetchTrendingCrypto sets Loading state and updates with Success when successful`(): Unit =
runTest {
// Given
val mockCoins = listOf<Coin>(/* your mock data here */)
coEvery { getTrendingCryptoUseCase.execute() } returns Result.Success(mockCoins)

// When
val result = homeViewModel.trendingCryptoState

// Then
assertEquals(TrendingCryptoState.Loading, homeViewModel.trendingCryptoState.value)
advanceUntilIdle()
assertEquals(
TrendingCryptoState.Success(mockCoins),
result.value
)
}

@Test
fun `fetchTrendingCrypto sets Loading state and updates with Error when unsuccessful`() =
runTest {
// Given
val expectedException = IOException("Network error")
coEvery { getTrendingCryptoUseCase.execute() } returns Result.Error(expectedException)

// When
val result = homeViewModel.trendingCryptoState

// Then
assertEquals(TrendingCryptoState.Loading, result.value)
advanceUntilIdle()
assertEquals(
TrendingCryptoState.Error(expectedException),
result.value
)
}
}