Does anyone have any idea why my uiState is not updated in the tests? The strange thing is that in the app running if it is updated, but when testing the viewmodel and send events does not update the state of the ui.
NOTE: I noticed that combine is not emitting the changes. No idea why.
class TabulatorViewModelTest {
private lateinit var tabulatorRepository: TabulatorRepository
private lateinit var calculateAdmissionPercentage: CalculateAdmissionPercentage
private lateinit var tabulatorViewModel: TabulatorViewModel
@Before
fun setUp() {
tabulatorRepository = FakeTabulatorRepository()
calculateAdmissionPercentage = CalculateAdmissionPercentage()
tabulatorViewModel = TabulatorViewModel(tabulatorRepository, calculateAdmissionPercentage)
}
@Test
fun `should display the correct initial state`() {
// Given
val expectedState = TabulatorState()
// When
val actualState = tabulatorViewModel.uiState.value
// Then
assertEquals(expectedState, actualState)
}
@Test
fun `should update biology score`() = runTest {
tabulatorViewModel.uiState.test {
var emission = awaitItem()
assertEquals(0, emission.biologyScore)
tabulatorViewModel.onEvent(TabulatorEvent.ChangeBiologyScore(50))
emission = awaitItem()
assertEquals(50, emission.biologyScore)
}
}
}
Error log
app.cash.turbine.TurbineAssertionError: No value produced in 3s
at app//app.cash.turbine.TurbineAssertionError$Companion.invoke(TurbineAssertionError.kt:32)
at app//app.cash.turbine.ChannelKt.awaitEvent(channel.kt:89)
at app//app.cash.turbine.ChannelKt$awaitEvent$1.invokeSuspend(channel.kt)
at app//kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at app//kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:102)
at app//kotlinx.coroutines.test.TestDispatcher.processEvent$kotlinx_coroutines_test(TestDispatcher.kt:24)
at app//kotlinx.coroutines.test.TestCoroutineScheduler.tryRunNextTaskUnless$kotlinx_coroutines_test(TestCoroutineScheduler.kt:99)
at app//kotlinx.coroutines.test.TestBuildersKt__TestBuildersKt$runTest$2$1$workRunner$1.invokeSuspend(TestBuilders.kt:322)
at app//kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at app//kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:104)
My viewmodel:
@HiltViewModel
class TabulatorViewModel @Inject constructor( tabulatorRepository: TabulatorRepository, private val calculateAdmissionPercentage: CalculateAdmissionPercentage ) : ViewModel() {
private var weightingCalculation: WeightingCalculation = Before2005()
private val _uiState = MutableStateFlow(TabulatorState())
private val _academicProgramsList = combine(
_uiState,
tabulatorRepository.getAcademicPrograms()
) { uiState, academicPrograms ->
when (uiState.academicProgramName) {
"" -> academicPrograms
else -> academicPrograms.filter {
it.name.contains(
uiState.academicProgramName,
ignoreCase = true
)
}
}
}
val uiState = combine(
_uiState,
_academicProgramsList
) { state, academicPrograms ->
state.copy(academicPrograms = academicPrograms)
}.stateIn(
viewModelScope,
SharingStarted.WhileSubscribed(stopTimeoutMillis = 5000L),
TabulatorState()
)
/**
* This function is called when the user interacts with the UI and triggers an event.
* It updates the UI state based on the event.
* @param event The event that was triggered by the user.
*/
fun onEvent(event: TabulatorEvent) {
when (event) {
is TabulatorEvent.ChangeBiologyScore -> {
_uiState.update {
it.copy(biologyScore = event.score)
}
}
is TabulatorEvent.ChangeChemistryScore -> {
_uiState.update {
it.copy(chemistryScore = event.score)
}
}
is TabulatorEvent.ChangeCriticalReadingScore -> {
_uiState.update {
it.copy(criticalReadingScore = event.score)
}
}
is TabulatorEvent.ChangeEnglishScore -> {
_uiState.update {
it.copy(englishScore = event.score)
}
}
is TabulatorEvent.ChangeGeographyScore -> {
_uiState.update {
it.copy(geographyScore = event.score)
}
}
is TabulatorEvent.ChangeHistoryScore -> {
_uiState.update {
it.copy(historyScore = event.score)
}
}
is TabulatorEvent.ChangeMathScore -> {
_uiState.update {
it.copy(mathScore = event.score)
}
}
is TabulatorEvent.ChangeNaturalSciencesScore -> {
_uiState.update {
it.copy(naturalSciencesScore = event.score)
}
}
is TabulatorEvent.ChangePhilosophyScore -> {
_uiState.update {
it.copy(philosophyScore = event.score)
}
}
is TabulatorEvent.ChangePhysicsScore -> {
_uiState.update {
it.copy(physicsScore = event.score)
}
}
is TabulatorEvent.ChangeSelectedTestType -> {
_uiState.update {
it.copy(selectedTestType = event.testType)
}
weightingCalculation = when (event.testType) {
is TestType.After2005 -> Before2014()
is TestType.After2014 -> After2014()
is TestType.Before2005 -> Before2005()
}
}
is TabulatorEvent.ChangeSocialSciencesScore -> {
_uiState.update {
it.copy(socialSciencesScore = event.score)
}
}
is TabulatorEvent.ChangeSpanishScore -> {
_uiState.update {
it.copy(spanishScore = event.score)
}
}
is TabulatorEvent.ChangeSelectedAcademicProgram -> {
_uiState.update {
it.copy(selectedAcademicProgram = event.academicProgram)
}
}
is TabulatorEvent.ChangeAcademicProgramName -> {
_uiState.update {
it.copy(
academicProgramName = event.name
)
}
}
is TabulatorEvent.EvaluateScores -> {
_uiState.update {
it.copy(isCalculatingResults = true)
}
viewModelScope.launch {
val icfesResult = IcfesResult(
chemistryScore = uiState.value.chemistryScore,
physicsScore = uiState.value.physicsScore,
biologyScore = uiState.value.biologyScore,
spanishScore = uiState.value.spanishScore,
philosophyScore = uiState.value.philosophyScore,
mathScore = uiState.value.mathScore,
historyScore = uiState.value.historyScore,
geographyScore = uiState.value.geographyScore,
englishScore = uiState.value.englishScore,
socialSciencesScore = uiState.value.socialSciencesScore,
criticalReadingScore = uiState.value.criticalReadingScore,
naturalSciencesScore = uiState.value.naturalSciencesScore
)
val admissionResults = mutableListOf<AdmissionResult>()
uiState.value.academicPrograms.forEach { academicProgram ->
val weighted = uiState.value.selectedAcademicProgram?.let {
weightingCalculation.calculateWeighted(
icfesResults = icfesResult,
academicProgram = it
)
} ?: 0f
val admissionPercentage =
calculateAdmissionPercentage(weighted, academicProgram.maximumScore)
admissionResults.add(
AdmissionResult(
academicProgram,
admissionPercentage,
weighted
)
)
}
admissionResults.sortByDescending { it.admissionPercentage }
_uiState.update {
it.copy(
admissionResults = admissionResults.toList(),
isCalculatingResults = false
)
}
}
}
}
}
}