Skip to content

Commit

Permalink
DashboardViewModel unit tests
Browse files Browse the repository at this point in the history
Signed-off-by: Pablo <[email protected]>
  • Loading branch information
Balcan committed Feb 8, 2024
1 parent ee50262 commit 9d64686
Show file tree
Hide file tree
Showing 5 changed files with 208 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import org.dhis2.android.rtsm.utils.NetworkUtils
import org.dhis2.commons.filters.FilterManager
import org.dhis2.commons.prefs.PreferenceProvider
import org.dhis2.commons.resources.ResourceManager
import org.dhis2.commons.viewmodel.DispatcherProvider
import org.dhis2.ui.ThemeManager
import org.dhis2.usescases.teiDashboard.DashboardRepositoryImpl
import org.dhis2.usescases.teiDashboard.DashboardViewModel
Expand Down Expand Up @@ -50,6 +51,8 @@ class TeiDashboardMobileActivityTest {
private var repository: DashboardRepositoryImpl = mock {

}

private var dispatcher:DispatcherProvider = mock()
var tei = Observable.just(
TrackedEntityInstance.builder()
.uid(TEI_Uid)
Expand Down Expand Up @@ -87,7 +90,8 @@ class TeiDashboardMobileActivityTest {
private fun initViewModel() {
viewModel = DashboardViewModel(
repository,
analyticsHelper
analyticsHelper,
dispatcher,
)

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
import org.dhis2.commons.viewmodel.DispatcherProvider
import org.dhis2.utils.AuthorityException
import org.dhis2.utils.analytics.ACTIVE_FOLLOW_UP
import org.dhis2.utils.analytics.AnalyticsHelper
Expand All @@ -21,6 +21,7 @@ import timber.log.Timber
class DashboardViewModel(
private val repository: DashboardRepository,
private val analyticsHelper: AnalyticsHelper,
private val dispatcher: DispatcherProvider,
) : ViewModel() {

private val eventUid = MutableLiveData<String>()
Expand Down Expand Up @@ -52,8 +53,8 @@ class DashboardViewModel(
}

private fun fetchDashboardModel() {
viewModelScope.launch {
val result = async(context = Dispatchers.IO) {
viewModelScope.launch(dispatcher.io()) {
val result = async {
repository.getDashboardModel()
}
try {
Expand All @@ -74,8 +75,8 @@ class DashboardViewModel(
}

private fun fetchGrouping() {
viewModelScope.launch {
val result = async(context = Dispatchers.IO) {
viewModelScope.launch(dispatcher.io()) {
val result = async {
repository.getGrouping()
}
try {
Expand Down Expand Up @@ -110,7 +111,7 @@ class DashboardViewModel(
repository.setFollowUp((dashboardModel.value as DashboardEnrollmentModel).currentEnrollment.uid())
_syncNeeded.value = true
_state.value = State.TO_UPDATE
analyticsHelper.setEvent(ACTIVE_FOLLOW_UP, _showFollowUpBar.toString(), FOLLOW_UP)
analyticsHelper.setEvent(ACTIVE_FOLLOW_UP, _showFollowUpBar.value.toString(), FOLLOW_UP)
updateDashboard()
}
}
Expand All @@ -137,8 +138,8 @@ class DashboardViewModel(
}

fun deleteEnrollment(onAuthorityError: () -> Unit) {
viewModelScope.launch {
val result = async(context = Dispatchers.IO) {
viewModelScope.launch(dispatcher.io()) {
val result = async {
dashboardModel.value.takeIf { it is DashboardEnrollmentModel }?.let {
repository.deleteEnrollmentIfPossible((it as DashboardEnrollmentModel).currentEnrollment.uid())
.blockingGet()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,21 @@ package org.dhis2.usescases.teiDashboard

import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import org.dhis2.commons.viewmodel.DispatcherProvider
import org.dhis2.utils.analytics.AnalyticsHelper

@Suppress("UNCHECKED_CAST")
class DashboardViewModelFactory(
val repository: DashboardRepository,
val analyticsHelper: AnalyticsHelper,
val dispatcher: DispatcherProvider,
) : ViewModelProvider.Factory {

override fun <T : ViewModel> create(modelClass: Class<T>): T {
return DashboardViewModel(
repository,
analyticsHelper,
dispatcher,
) as T
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import org.dhis2.commons.di.dagger.PerActivity
import org.dhis2.commons.matomo.MatomoAnalyticsController
import org.dhis2.commons.prefs.PreferenceProvider
import org.dhis2.commons.schedulers.SchedulerProvider
import org.dhis2.commons.viewmodel.DispatcherProvider
import org.dhis2.data.forms.EnrollmentFormRepository
import org.dhis2.data.forms.FormRepository
import org.dhis2.data.forms.dataentry.EnrollmentRuleEngineRepository
Expand Down Expand Up @@ -123,7 +124,8 @@ class TeiDashboardModule(
fun providesViewModelFactory(
repository: DashboardRepository,
analyticsHelper: AnalyticsHelper,
dispatcher: DispatcherProvider,
): DashboardViewModelFactory {
return DashboardViewModelFactory(repository, analyticsHelper)
return DashboardViewModelFactory(repository, analyticsHelper, dispatcher)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
package org.dhis2.usescases.teiDashboard

import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import io.reactivex.Observable
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.setMain
import org.dhis2.commons.viewmodel.DispatcherProvider
import org.dhis2.utils.analytics.ACTIVE_FOLLOW_UP
import org.dhis2.utils.analytics.AnalyticsHelper
import org.dhis2.utils.analytics.FOLLOW_UP
import org.hisp.dhis.android.core.common.State
import org.hisp.dhis.android.core.enrollment.Enrollment
import org.hisp.dhis.android.core.enrollment.EnrollmentStatus
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.mockito.kotlin.any
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever

class DashboardViewModelTest {

@get:Rule
val instantExecutorRule = InstantTaskExecutorRule()

private val repository: DashboardRepository = mock()
private val analyticsHelper: AnalyticsHelper = mock()
private val testingDispatcher = StandardTestDispatcher()

@OptIn(ExperimentalCoroutinesApi::class)
@Before
fun setUp() {
Dispatchers.setMain(testingDispatcher)
}

@Test
fun shouldFetchEnrollmentModel() {
mockEnrollmentModel()
mockGrouping(true)

val dashboardViewModel = getViewModel()

with(dashboardViewModel) {
assertTrue(dashboardModel.value == mockedEnrollmentModel)
assertTrue(showFollowUpBar.value)
assertTrue(!syncNeeded.value)
assertTrue(showStatusBar.value == EnrollmentStatus.ACTIVE)
assertTrue(state.value == State.SYNCED)
}
}

@Test
fun shouldFetchTeiModel() {
mockTeiModel()
mockGrouping(false)

val dashboardViewModel = getViewModel()

with(dashboardViewModel) {
assertTrue(dashboardModel.value == mockedTeiModel)
assertTrue(!showFollowUpBar.value)
assertTrue(!syncNeeded.value)
assertTrue(showStatusBar.value == null)
assertTrue(state.value == null)
}
}

@Test
fun shouldSetGrouping() {
mockEnrollmentModel()
mockGrouping(false)

val dashboardViewModel = getViewModel()

with(dashboardViewModel) {
assertTrue(groupByStage.value == false)
setGrouping(true)
verify(repository).setGrouping(true)
assertTrue(groupByStage.value == true)
}
}

@Test
fun shouldUpdateEventUid() {
mockEnrollmentModel()
mockGrouping(false)

with(getViewModel()) {
assertTrue(eventUid().value == null)
updateEventUid("eventUid")
assertTrue(eventUid().value == "eventUid")
}
}

@Test
fun shouldSetFollowUpOnEnrollment() {
mockEnrollmentModel()
mockGrouping(false)

with(getViewModel()) {
onFollowUp()
verify(repository).setFollowUp("enrollmentUid")
assertTrue(state.value == State.TO_UPDATE)
verify(analyticsHelper).setEvent(ACTIVE_FOLLOW_UP, "false", FOLLOW_UP)
}
}

@Test
fun shouldUpdateEnrollmentStatus() {
mockEnrollmentModel()
mockGrouping(false)

with(getViewModel()) {
whenever(repository.updateEnrollmentStatus(any(), any())) doReturn Observable.just(
StatusChangeResultCode.CHANGED,
)
updateEnrollmentStatus(EnrollmentStatus.COMPLETED)
verify(repository).updateEnrollmentStatus("enrollmentUid", EnrollmentStatus.COMPLETED)
assertTrue(showStatusBar.value == EnrollmentStatus.COMPLETED)
assertTrue(syncNeeded.value)
assertTrue(state.value == State.TO_UPDATE)
assertTrue(updateEnrollment.value == true)
}
}

@Test
fun shouldShowMessageIfErrorWhileUpdatingEnrollmentStatus() {
mockEnrollmentModel()
mockGrouping(false)

with(getViewModel()) {
whenever(repository.updateEnrollmentStatus(any(), any())) doReturn Observable.just(
StatusChangeResultCode.FAILED,
)
updateEnrollmentStatus(EnrollmentStatus.COMPLETED)
assertTrue(showStatusErrorMessages.value == StatusChangeResultCode.FAILED)
}
}

private fun getViewModel() = DashboardViewModel(
repository,
analyticsHelper,
object :
DispatcherProvider {
override fun io(): CoroutineDispatcher {
return testingDispatcher
}

override fun computation(): CoroutineDispatcher {
return testingDispatcher
}

override fun ui(): CoroutineDispatcher {
return testingDispatcher
}
},
).also {
testingDispatcher.scheduler.advanceUntilIdle()
}

private fun mockEnrollmentModel() {
whenever(repository.getDashboardModel()) doReturn mockedEnrollmentModel
whenever(mockedEnrollmentModel.currentEnrollment) doReturn mockedEnrollment
}

private fun mockTeiModel() {
whenever(repository.getDashboardModel()) doReturn mockedTeiModel
}

private fun mockGrouping(group: Boolean) {
whenever(repository.getGrouping()) doReturn group
}

private val mockedEnrollmentModel: DashboardEnrollmentModel = mock()
private val mockedTeiModel: DashboardTEIModel = mock()
private val mockedEnrollment: Enrollment = mock {
on { uid() } doReturn "enrollmentUid"
on { followUp() } doReturn true
on { aggregatedSyncState() } doReturn State.SYNCED
on { status() } doReturn EnrollmentStatus.ACTIVE
}
}

0 comments on commit 9d64686

Please sign in to comment.