REDI-95: ViewModel unit tests (JUnit5 + Turbine + AssertK + fakes)
Test CharacterListViewModel and CharacterDetailViewModel entirely through their MVI surface with a FakeCharacterRepository (a fake, not a mock) and a directly constructed SavedStateHandle, on StandardTestDispatcher. Coverage: happy path, error -> UiText + snackbar Event, pagination end-reached, the in-flight and duplicate next-page guards, process-death restore, and both branches of OnRetry. Also a domain test for GetCharactersPageUseCase (delegation + error propagation).
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
package com.example.architecture.feature.characters.domain.usecase
|
||||
|
||||
import assertk.assertThat
|
||||
import assertk.assertions.isEqualTo
|
||||
import assertk.assertions.isInstanceOf
|
||||
import com.example.architecture.core.domain.DataError
|
||||
import com.example.architecture.core.domain.Result
|
||||
import com.example.architecture.feature.characters.domain.CharacterRepository
|
||||
import com.example.architecture.feature.characters.domain.model.Character
|
||||
import com.example.architecture.feature.characters.domain.model.CharacterDetails
|
||||
import com.example.architecture.feature.characters.domain.model.CharacterStatus
|
||||
import com.example.architecture.feature.characters.domain.model.CharactersPage
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
/**
|
||||
* Tests for the (thin pass-through) [GetCharactersPageUseCase]: it must forward the requested page to
|
||||
* the repository and return its result verbatim — success and error alike. Pure JVM test on the
|
||||
* JUnit 5 platform (see DomainModuleConventionPlugin); collaborator is a hand-written fake.
|
||||
*/
|
||||
class GetCharactersPageUseCaseTest {
|
||||
|
||||
@Test
|
||||
fun `returns the repository page on success`() = runBlocking {
|
||||
val page = CharactersPage(characters = listOf(domainCharacter(1)), nextPage = 2)
|
||||
val useCase = GetCharactersPageUseCase(FakeCharacterRepository(pageResult = Result.Success(page)))
|
||||
|
||||
val result = useCase(page = 1)
|
||||
|
||||
assertThat(result).isEqualTo(Result.Success(page))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `propagates the repository error`() = runBlocking {
|
||||
val useCase = GetCharactersPageUseCase(
|
||||
FakeCharacterRepository(pageResult = Result.Error(DataError.Network.SERVER_ERROR)),
|
||||
)
|
||||
|
||||
val result = useCase(page = 1)
|
||||
|
||||
assertThat(result).isInstanceOf(Result.Error::class)
|
||||
assertThat((result as Result.Error).error).isEqualTo(DataError.Network.SERVER_ERROR)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `forwards the requested page number`() = runBlocking {
|
||||
val fake = FakeCharacterRepository(
|
||||
pageResult = Result.Success(CharactersPage(characters = emptyList(), nextPage = null)),
|
||||
)
|
||||
val useCase = GetCharactersPageUseCase(fake)
|
||||
|
||||
useCase(page = 7)
|
||||
|
||||
assertThat(fake.lastRequestedPage).isEqualTo(7)
|
||||
}
|
||||
|
||||
private class FakeCharacterRepository(
|
||||
private val pageResult: Result<CharactersPage, DataError>,
|
||||
) : CharacterRepository {
|
||||
var lastRequestedPage: Int? = null
|
||||
private set
|
||||
|
||||
override suspend fun getCharacters(page: Int): Result<CharactersPage, DataError> {
|
||||
lastRequestedPage = page
|
||||
return pageResult
|
||||
}
|
||||
|
||||
override suspend fun getCharacterDetails(id: Int): Result<CharacterDetails, DataError> =
|
||||
Result.Error(DataError.Network.NOT_FOUND)
|
||||
}
|
||||
|
||||
private fun domainCharacter(id: Int) = Character(
|
||||
id = id,
|
||||
name = "Character $id",
|
||||
status = CharacterStatus.ALIVE,
|
||||
species = "Human",
|
||||
imageUrl = "https://example.com/$id.png",
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user