Skip to content

Commit

Permalink
Adding ExchangeRatesFlowTest
Browse files Browse the repository at this point in the history
  • Loading branch information
philipplackner committed Aug 7, 2023
1 parent 04e753e commit b63f20c
Show file tree
Hide file tree
Showing 8 changed files with 231 additions and 1 deletion.
25 changes: 25 additions & 0 deletions common/test/src/main/java/com/ivy/MainCoroutineExtension.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.ivy

import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestDispatcher
import kotlinx.coroutines.test.resetMain
import kotlinx.coroutines.test.setMain
import org.junit.jupiter.api.extension.AfterEachCallback
import org.junit.jupiter.api.extension.BeforeEachCallback
import org.junit.jupiter.api.extension.ExtensionContext

@OptIn(ExperimentalCoroutinesApi::class)
class MainCoroutineExtension(
val testDispatcher: TestDispatcher = StandardTestDispatcher()
): BeforeEachCallback, AfterEachCallback {

override fun beforeEach(context: ExtensionContext?) {
Dispatchers.setMain(testDispatcher)
}

override fun afterEach(context: ExtensionContext?) {
Dispatchers.resetMain()
}
}
21 changes: 21 additions & 0 deletions common/test/src/main/java/com/ivy/TestDispatchers.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.ivy

import com.ivy.core.domain.pure.util.DispatcherProvider
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestDispatcher

@OptIn(ExperimentalCoroutinesApi::class)
class TestDispatchers(
val testDispatcher: TestDispatcher = StandardTestDispatcher()
): DispatcherProvider {
override val main: CoroutineDispatcher
get() = testDispatcher
override val io: CoroutineDispatcher
get() = testDispatcher
override val default: CoroutineDispatcher
get() = testDispatcher
override val unconfined: CoroutineDispatcher
get() = testDispatcher
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.ivy.core.domain.action.exchange

import com.ivy.core.domain.action.SharedFlowAction
import com.ivy.core.domain.action.settings.basecurrency.BaseCurrencyFlow
import com.ivy.core.domain.pure.util.DispatcherProvider
import com.ivy.core.persistence.dao.exchange.ExchangeRateDao
import com.ivy.core.persistence.dao.exchange.ExchangeRateOverrideDao
import com.ivy.data.exchange.ExchangeRates
Expand All @@ -26,6 +27,7 @@ class ExchangeRatesFlow @Inject constructor(
private val baseCurrencyFlow: BaseCurrencyFlow,
private val exchangeRateDao: ExchangeRateDao,
private val exchangeRateOverrideDao: ExchangeRateOverrideDao,
private val dispatchers: DispatcherProvider
) : SharedFlowAction<ExchangeRates>() {
override fun initialValue(): ExchangeRates = ExchangeRates(
baseCurrency = "",
Expand Down Expand Up @@ -55,5 +57,5 @@ class ExchangeRatesFlow @Inject constructor(
rates = ratesMap,
)
}
}.flowOn(Dispatchers.Default)
}.flowOn(dispatchers.default)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.ivy.core.domain.pure.util

import kotlinx.coroutines.CoroutineDispatcher

interface DispatcherProvider {
val main: CoroutineDispatcher
val io: CoroutineDispatcher
val default: CoroutineDispatcher
val unconfined: CoroutineDispatcher
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.ivy.core.domain.pure.util

import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers

class StandardDispatchers: DispatcherProvider {
override val main: CoroutineDispatcher
get() = Dispatchers.Main
override val io: CoroutineDispatcher
get() = Dispatchers.IO
override val default: CoroutineDispatcher
get() = Dispatchers.Default
override val unconfined: CoroutineDispatcher
get() = Dispatchers.Unconfined
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.ivy.core.domain.action.exchange

import com.ivy.core.persistence.entity.exchange.ExchangeRateEntity
import com.ivy.core.persistence.entity.exchange.ExchangeRateOverrideEntity
import com.ivy.data.SyncState
import java.time.Instant

fun exchangeRateEntity(
currency: String,
rate: Double
): ExchangeRateEntity {
return ExchangeRateEntity(
baseCurrency = "EUR",
currency = currency,
rate = rate,
provider = null
)
}

fun exchangeRateOverrideEntity(
currency: String,
rate: Double
): ExchangeRateOverrideEntity {
return ExchangeRateOverrideEntity(
baseCurrency = "EUR",
currency = currency,
rate = rate,
sync = SyncState.Syncing,
lastUpdated = Instant.now()
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.ivy.core.domain.action.exchange

import com.ivy.core.persistence.dao.exchange.ExchangeRateOverrideDao
import com.ivy.core.persistence.entity.exchange.ExchangeRateOverrideEntity
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.update

class ExchangeRateOverrideDaoFake: ExchangeRateOverrideDao {

private val rates = MutableStateFlow<List<ExchangeRateOverrideEntity>>(emptyList())

override suspend fun save(values: List<ExchangeRateOverrideEntity>) {
rates.value = values
}

override suspend fun findAllBlocking(): List<ExchangeRateOverrideEntity> {
return rates.value
}

override fun findAllByBaseCurrency(baseCurrency: String): Flow<List<ExchangeRateOverrideEntity>> {
return rates.map { overrides ->
overrides.filter { it.baseCurrency.uppercase() == baseCurrency.uppercase() }
}
}

override suspend fun deleteByBaseCurrencyAndCurrency(baseCurrency: String, currency: String) {
rates.update {
it.filter { entity ->
entity.baseCurrency != baseCurrency && entity.currency != currency
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package com.ivy.core.domain.action.exchange

import app.cash.turbine.test
import assertk.assertThat
import assertk.assertions.hasSize
import assertk.assertions.isEqualTo
import com.ivy.MainCoroutineExtension
import com.ivy.TestDispatchers
import com.ivy.core.domain.action.settings.basecurrency.BaseCurrencyFlow
import com.ivy.core.persistence.entity.exchange.ExchangeRateEntity
import com.ivy.core.persistence.entity.exchange.ExchangeRateOverrideEntity
import com.ivy.data.SyncState
import io.mockk.every
import io.mockk.mockk
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.TestDispatcher
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.Assertions.*
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import org.junit.jupiter.api.extension.RegisterExtension
import java.time.Instant
import java.time.LocalDateTime

@OptIn(ExperimentalCoroutinesApi::class)
class ExchangeRatesFlowTest {

private lateinit var exchangeRatesFlow: ExchangeRatesFlow
private lateinit var baseCurrencyFlow: BaseCurrencyFlow
private lateinit var exchangeRateDao: ExchangeRateDaoFake
private lateinit var exchangeRateOverrideDao: ExchangeRateOverrideDaoFake

companion object {
@JvmField
@RegisterExtension
val mainCoroutineExtension = MainCoroutineExtension()
}

@BeforeEach
fun setUp() {
baseCurrencyFlow = mockk()
every { baseCurrencyFlow.invoke() } returns flowOf("", "EUR")

exchangeRateDao = ExchangeRateDaoFake()
exchangeRateOverrideDao = ExchangeRateOverrideDaoFake()

val testDispatchers = TestDispatchers(mainCoroutineExtension.testDispatcher)

exchangeRatesFlow = ExchangeRatesFlow(
baseCurrencyFlow = baseCurrencyFlow,
exchangeRateDao = exchangeRateDao,
exchangeRateOverrideDao = exchangeRateOverrideDao,
dispatchers = testDispatchers
)
}

@Test
fun `Test exchange rates flow emissions`() = runTest {
val exchangeRates = listOf(
exchangeRateEntity("USD", 1.3),
exchangeRateEntity("CAD", 1.7),
exchangeRateEntity("AUD", 1.9),
)
val exchangeRateOverrides = listOf(
exchangeRateOverrideEntity("CAD", 1.5)
)
exchangeRatesFlow().test {
awaitItem() // Initial emission, ignore

exchangeRateDao.save(exchangeRates)
exchangeRateOverrideDao.save(exchangeRateOverrides)
val rates1 = awaitItem()

assertThat(rates1.rates).hasSize(3)
assertThat(rates1.rates["USD"]).isEqualTo(1.3)
assertThat(rates1.rates["CAD"]).isEqualTo(1.5) // Override rate
assertThat(rates1.rates["AUD"]).isEqualTo(1.9)

exchangeRateOverrideDao.save(emptyList())

val rates2 = awaitItem()

assertThat(rates2.rates).hasSize(3)
assertThat(rates2.rates["USD"]).isEqualTo(1.3)
assertThat(rates2.rates["CAD"]).isEqualTo(1.7) // Real rate
assertThat(rates2.rates["AUD"]).isEqualTo(1.9)
}
}
}

0 comments on commit b63f20c

Please sign in to comment.