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,81 @@
@file:Suppress("ktlint")

package ir.composenews.uimarket.mapper

import io.kotest.core.spec.style.StringSpec
import io.kotest.matchers.shouldBe
import ir.composenews.domain.model.Market
import ir.composenews.uimarket.model.MarketModel

class MarketModelMapperTest : StringSpec({

"Given a domain Market, When mapped to MarketModel, Then all fields are correctly mapped" {
val market = Market(
id = "btc",
name = "Bitcoin",
symbol = "BTC",
currentPrice = 50000.0,
priceChangePercentage24h = 2.5,
imageUrl = "https://example.com/btc.png",
isFavorite = false,
)

val model = market.toMarketModel()

model.id shouldBe market.id
model.name shouldBe market.name
model.symbol shouldBe market.symbol
model.currentPrice shouldBe market.currentPrice
model.priceChangePercentage24h shouldBe market.priceChangePercentage24h
model.imageUrl shouldBe market.imageUrl
model.isFavorite shouldBe market.isFavorite
}

"Given a favorited Market, When mapped to MarketModel, Then isFavorite is true" {
val market = Market(
id = "eth",
name = "Ethereum",
symbol = "ETH",
currentPrice = 3000.0,
priceChangePercentage24h = -1.5,
imageUrl = "",
isFavorite = true,
)

val model = market.toMarketModel()

model.isFavorite shouldBe true
}

"Given a MarketModel, When mapped to Market and back to MarketModel, Then all fields are preserved" {
val original = MarketModel(
id = "sol",
name = "Solana",
symbol = "SOL",
currentPrice = 150.0,
priceChangePercentage24h = 10.0,
imageUrl = "https://example.com/sol.png",
isFavorite = true,
)

val roundTripped = original.toMarket().toMarketModel()

roundTripped shouldBe original
}

"Given a Market with zero price change, When mapped to MarketModel, Then priceChangePercentage24h is 0.0" {
val market = Market(
id = "usdc",
name = "USD Coin",
symbol = "USDC",
currentPrice = 1.0,
priceChangePercentage24h = 0.0,
imageUrl = "",
isFavorite = false,
)

val model = market.toMarketModel()

model.priceChangePercentage24h shouldBe 0.0
}
})
Original file line number Diff line number Diff line change
Expand Up @@ -138,4 +138,46 @@ class MarketRepositoryImplTest : StringSpec({

actualMarkets.shouldBeInstanceOf<Resource.Error<Errors>>()
}

"Given a favorited market, When ToggleFavoriteMarket is called, Then updates market favorite status to FALSE" {
val market = Market("1", "Bitcoin", "BTC", 50000.0, 5.0, "url", true)
coEvery { marketDao.updateFavoriteStatus(any(), any()) } just Runs

repository.toggleFavoriteMarket(market)

coVerify(exactly = 1) {
marketDao.updateFavoriteStatus(id = "1", isFavorite = FALSE)
}
}

"Given API throws exception, When SyncMarketList is called, Then does not update database" {
coEvery {
api.getMarkets(any(), any(), any(), any(), any())
} returns ApiResponse.Failure.Exception(IOException())

repository.syncMarketList()

coVerify(exactly = 0) { marketDao.insertMarket(any()) }
}

"Given API provides multiple markets, When SyncMarketList is called, Then each market is inserted" {
val responses = listOf(
MarketResponse("1", "Bitcoin", "BTC", 50000.0, 5.0, "url"),
MarketResponse("2", "Ethereum", "ETH", 3000.0, 2.0, "url"),
)
coEvery { api.getMarkets(any(), any(), any(), any(), any()) } returns ApiResponse.Success(responses)
coEvery { marketDao.insertMarket(any()) } just Runs

repository.syncMarketList()

coVerify(exactly = responses.size) { marketDao.insertMarket(any()) }
}

"Given database is empty, When GetMarketList is called, Then returns empty list" {
every { marketDao.getMarketList() } returns flowOf(emptyList())

val result = repository.getMarketList().first()

result shouldBe emptyList()
}
})
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,19 @@
"PackageNaming",
"PackageName",
"ktlint:standard:class-signature",
"ktlint:standard:multiline-expression-wrapping",
)

package ir.composenews.domain.use_case

import io.kotest.core.spec.style.StringSpec
import io.kotest.matchers.shouldBe
import io.mockk.every
import io.mockk.mockk
import io.mockk.verify
import ir.composenews.domain.model.Market
import ir.composenews.domain.repository.MarketRepository
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flowOf

class GetFavoriteMarketListUseCaseTest : StringSpec({
Expand All @@ -29,4 +33,29 @@ class GetFavoriteMarketListUseCaseTest : StringSpec({
marketRepository.getFavoriteMarketList()
}
}

"Given repository returns favorite markets, When invoked, Then flow emits those markets" {
val market = Market(
id = "btc",
name = "Bitcoin",
symbol = "BTC",
currentPrice = 50000.0,
priceChangePercentage24h = 2.5,
imageUrl = "",
isFavorite = true,
)
every { marketRepository.getFavoriteMarketList() } returns flowOf(listOf(market))

val result = getFavoriteMarketListUseCase.invoke().first()

result shouldBe listOf(market)
}

"Given repository returns empty list, When invoked, Then flow emits empty list" {
every { marketRepository.getFavoriteMarketList() } returns flowOf(emptyList())

val result = getFavoriteMarketListUseCase.invoke().first()

result shouldBe emptyList()
}
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
@file:Suppress("PackageNaming", "PackageName", "ktlint:standard:class-signature")

package ir.composenews.domain.use_case

import io.kotest.core.spec.style.StringSpec
import io.kotest.matchers.shouldBe
import io.mockk.every
import io.mockk.mockk
import io.mockk.verify
import ir.composenews.domain.model.MarketChart
import ir.composenews.domain.repository.MarketRepository
import ir.composenews.network.Resource
import kotlinx.collections.immutable.persistentListOf
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flowOf

class GetMarketChartUseCaseTest : StringSpec({

lateinit var repository: MarketRepository
lateinit var useCase: GetMarketChartUseCase

beforeTest {
repository = mockk(relaxed = true)
useCase = GetMarketChartUseCase(repository = repository)
}

"Given repository, When invoked with a market id, Then fetchChart is called with that id" {
val id = "bitcoin"
every { repository.fetchChart(id = id) } returns flowOf()

useCase.invoke(id)

verify(exactly = 1) { repository.fetchChart(id = id) }
}

"Given repository returns chart data, When invoked, Then flow emits that chart" {
val id = "bitcoin"
val chart = MarketChart(prices = persistentListOf(Pair(1000L, 50000.0)))
every { repository.fetchChart(id = id) } returns flowOf(Resource.Success(chart))

val result = useCase.invoke(id).first()

result shouldBe Resource.Success(chart)
}
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
@file:Suppress("PackageNaming", "PackageName", "ktlint:standard:class-signature")

package ir.composenews.domain.use_case

import io.kotest.core.spec.style.StringSpec
import io.kotest.matchers.shouldBe
import io.mockk.every
import io.mockk.mockk
import io.mockk.verify
import ir.composenews.domain.model.MarketDetail
import ir.composenews.domain.repository.MarketRepository
import ir.composenews.network.Resource
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flowOf

class GetMarketDetailUseCaseTest : StringSpec({

lateinit var repository: MarketRepository
lateinit var useCase: GetMarketDetailUseCase

beforeTest {
repository = mockk(relaxed = true)
useCase = GetMarketDetailUseCase(repository = repository)
}

"Given repository, When invoked with a market id, Then fetchDetail is called with that id" {
val id = "bitcoin"
every { repository.fetchDetail(id = id) } returns flowOf()

useCase.invoke(id)

verify(exactly = 1) { repository.fetchDetail(id = id) }
}

"Given repository returns market detail, When invoked, Then flow emits that detail" {
val id = "bitcoin"
val detail = MarketDetail(id = id, name = "Bitcoin", marketCapRank = 1, marketData = null)
every { repository.fetchDetail(id = id) } returns flowOf(Resource.Success(detail))

val result = useCase.invoke(id).first()

result shouldBe Resource.Success(detail)
}
})
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,25 @@
"PackageNaming",
"PackageName",
"ktlint:standard:class-signature",
"ktlint:standard:multiline-expression-wrapping",
)

package ir.composenews.domain.use_case

import io.kotest.core.spec.style.StringSpec
import io.kotest.matchers.shouldBe
import io.mockk.Ordering
import io.mockk.Runs
import io.mockk.coEvery
import io.mockk.coVerify
import io.mockk.every
import io.mockk.just
import io.mockk.mockk
import io.mockk.verify
import ir.composenews.domain.model.Market
import ir.composenews.domain.repository.MarketRepository
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flowOf

class GetMarketListUseCaseTest : StringSpec({

Expand All @@ -31,4 +39,34 @@ class GetMarketListUseCaseTest : StringSpec({

coVerify { repository.syncMarketList() }
}

"Given repository returns market list, When invoked, Then flow emits those markets" {
val market = Market(
id = "btc",
name = "Bitcoin",
symbol = "BTC",
currentPrice = 50000.0,
priceChangePercentage24h = 2.5,
imageUrl = "",
isFavorite = false,
)
coEvery { repository.syncMarketList() } just Runs
every { repository.getMarketList() } returns flowOf(listOf(market))

val result = useCase.invoke().first()

result shouldBe listOf(market)
}

"Given invoked, When repository methods are called, Then syncMarketList is called before getMarketList" {
coEvery { repository.syncMarketList() } just Runs
every { repository.getMarketList() } returns flowOf(emptyList())

useCase.invoke()

coVerify(ordering = Ordering.SEQUENCE) {
repository.syncMarketList()
repository.getMarketList()
}
}
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
@file:Suppress("PackageNaming", "PackageName", "ktlint:standard:class-signature")

package ir.composenews.domain.use_case

import io.kotest.core.spec.style.StringSpec
import io.mockk.Runs
import io.mockk.coEvery
import io.mockk.coVerify
import io.mockk.just
import io.mockk.mockk
import ir.composenews.domain.repository.MarketRepository

class SyncMarketListUseCaseTest : StringSpec({

lateinit var repository: MarketRepository
lateinit var useCase: SyncMarketListUseCase

beforeTest {
repository = mockk(relaxed = true)
useCase = SyncMarketListUseCase(repository = repository)
}

"Given repository, When invoked, Then syncMarketList is called exactly once" {
coEvery { repository.syncMarketList() } just Runs

useCase.invoke()

coVerify(exactly = 1) { repository.syncMarketList() }
}
})
Loading
Loading