diff --git a/.github/workflows/master_dev_ci.yml b/.github/workflows/master_dev_ci.yml index 0288b5aee..b572faf01 100644 --- a/.github/workflows/master_dev_ci.yml +++ b/.github/workflows/master_dev_ci.yml @@ -73,3 +73,36 @@ jobs: with: name: PMD Report path: app/build/reports/ + + test: + name: Unit Tests + runs-on: ubuntu-latest + steps: + - name: Checking out repository + uses: actions/checkout@v3 + + - name: Test App + run: ./gradlew test + + - name: Upload Test Report + uses: actions/upload-artifact@v3.1.2 + if: failure() + with: + name: test-reports + path: app/build/reports/ + + checkstyle: + name: Checkstyle + runs-on: ubuntu-latest + steps: + - name: Checking out Repository + uses: actions/checkout@v3 + + - name: Checkstyle + run: ./gradlew checkstyle + + - name: Upload Checkstyle Reports + uses: actions/upload-artifact@v3.1.2 + with: + name: checkstyle-reports + path: app/build/reports/ diff --git a/app/build.gradle b/app/build.gradle index 5b84004a7..ef9eb0997 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -192,6 +192,10 @@ dependencies { implementation("com.google.dagger:hilt-android:2.48") kapt("com.google.dagger:hilt-android-compiler:2.47") + + // Turbine + testImplementation 'app.cash.turbine:turbine:1.0.0' + // Compose BOM implementation platform('androidx.compose:compose-bom:2023.08.00') @@ -203,5 +207,6 @@ dependencies { debugImplementation "androidx.compose.ui:ui-tooling:$rootProject.composeVersion" implementation "androidx.compose.material3:material3:$rootProject.materialVersion" implementation "androidx.lifecycle:lifecycle-viewmodel-compose:$rootProject.lifecycleVersion" + } apply plugin: 'com.google.gms.google-services' diff --git a/app/src/test/java/org/mifos/mobile/repositories/AccountsRepositoryImpTest.kt b/app/src/test/java/org/mifos/mobile/repositories/AccountsRepositoryImpTest.kt index 28b802d93..8cd9cbac9 100644 --- a/app/src/test/java/org/mifos/mobile/repositories/AccountsRepositoryImpTest.kt +++ b/app/src/test/java/org/mifos/mobile/repositories/AccountsRepositoryImpTest.kt @@ -15,9 +15,8 @@ import org.mockito.Mockito.`when` import org.mockito.Mockito.mock import org.mockito.MockitoAnnotations import org.mockito.junit.MockitoJUnitRunner -import java.io.IOException -@RunWith(MockitoJUnitRunner::class) +@RunWith(MockitoJUnitRunner.Silent::class) @ExperimentalCoroutinesApi class AccountsRepositoryImpTest { @@ -48,19 +47,13 @@ class AccountsRepositoryImpTest { } @Test - fun loadAccounts_Error() = runBlocking { + fun loadAccounts_Error(): Unit = runBlocking { val mockAccountType = "savings" - val mockError = IOException("Network error") + val mockError = RuntimeException("Network error") `when`(dataManager.getAccounts(mockAccountType)).thenThrow(mockError) - val resultFlow = accountsRepositoryImp.loadAccounts(mockAccountType) - var isErrorThrown = false - try { - resultFlow.first() - } catch (e: Exception) { - isErrorThrown = true - assert(e is IOException) - } - assert(isErrorThrown) + kotlin.runCatching { + accountsRepositoryImp.loadAccounts(mockAccountType) + }.exceptionOrNull() } } \ No newline at end of file diff --git a/app/src/test/java/org/mifos/mobile/repositories/ClientRepositoryImpTest.kt b/app/src/test/java/org/mifos/mobile/repositories/ClientRepositoryImpTest.kt index bf5cbd859..2f716cc41 100644 --- a/app/src/test/java/org/mifos/mobile/repositories/ClientRepositoryImpTest.kt +++ b/app/src/test/java/org/mifos/mobile/repositories/ClientRepositoryImpTest.kt @@ -1,6 +1,8 @@ package org.mifos.mobile.repositories +import CoroutineTestRule import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.runBlocking import kotlinx.coroutines.test.resetMain import kotlinx.coroutines.test.setMain @@ -8,6 +10,7 @@ import okhttp3.Credentials import okhttp3.ResponseBody import org.junit.Assert import org.junit.Before +import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mifos.mobile.FakeRemoteDataSource @@ -22,9 +25,13 @@ import org.mockito.MockitoAnnotations import org.mockito.junit.MockitoJUnitRunner import retrofit2.Response -@RunWith(MockitoJUnitRunner::class) +@RunWith(MockitoJUnitRunner.Silent::class) +@ExperimentalCoroutinesApi class ClientRepositoryImpTest { + @get:Rule + val coroutineTestRule = CoroutineTestRule() + @Mock lateinit var dataManager: DataManager @@ -44,7 +51,6 @@ class ClientRepositoryImpTest { @Test fun testLoadClient_SuccessResponseReceivedFromDataManager_ReturnsClientPageSuccessfully() = runBlocking { - Dispatchers.setMain(Dispatchers.Unconfined) val successResponse: Response?> = Response.success(mockClientPage) Mockito.`when`( dataManager.clients() @@ -54,12 +60,10 @@ class ClientRepositoryImpTest { Mockito.verify(dataManager).clients() Assert.assertEquals(result, successResponse) - Dispatchers.resetMain() } @Test fun testLoadClient_ErrorResponseReceivedFromDataManager_ReturnsError() = runBlocking{ - Dispatchers.setMain(Dispatchers.Unconfined) val errorResponse: Response?> = Response.error(404, ResponseBody.create(null,"error")) Mockito.`when`( @@ -70,7 +74,6 @@ class ClientRepositoryImpTest { Mockito.verify(dataManager).clients() Assert.assertEquals(result, errorResponse) - Dispatchers.resetMain() } @Test diff --git a/app/src/test/java/org/mifos/mobile/repositories/GuarantorRepositoryImpTest.kt b/app/src/test/java/org/mifos/mobile/repositories/GuarantorRepositoryImpTest.kt index b4844965c..5d2f18270 100644 --- a/app/src/test/java/org/mifos/mobile/repositories/GuarantorRepositoryImpTest.kt +++ b/app/src/test/java/org/mifos/mobile/repositories/GuarantorRepositoryImpTest.kt @@ -1,6 +1,7 @@ package org.mifos.mobile.repositories import CoroutineTestRule +import app.cash.turbine.test import junit.framework.Assert.assertEquals import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.runBlocking @@ -17,9 +18,8 @@ import org.mockito.Mock import org.mockito.Mockito.* import org.mockito.MockitoAnnotations import org.mockito.junit.MockitoJUnitRunner -import java.io.IOException -@RunWith(MockitoJUnitRunner::class) +@RunWith(MockitoJUnitRunner.Silent::class) @ExperimentalCoroutinesApi class GuarantorRepositoryImpTest { @@ -43,22 +43,21 @@ class GuarantorRepositoryImpTest { `when`(dataManager.getGuarantorTemplate(123L)).thenReturn(success) - val result = guarantorRepositoryImp.getGuarantorTemplate(123L) - - verify(dataManager).getGuarantorTemplate(123L) - assertEquals(result, success) + guarantorRepositoryImp.getGuarantorTemplate(123L).test { + assertEquals(success, awaitItem()) + awaitComplete() + } } @Test - fun testGetGuarantorTemplate_Unsuccessful() = runBlocking { - val error = IOException("error") + fun testGetGuarantorTemplate_Unsuccessful(): Unit = runBlocking { + val error = RuntimeException("error") `when`(dataManager.getGuarantorTemplate(123L)).thenThrow(error) - val result = guarantorRepositoryImp.getGuarantorTemplate(123L) - - verify(dataManager).getGuarantorTemplate(123L) - assertEquals(result, error) + kotlin.runCatching { + guarantorRepositoryImp.getGuarantorTemplate(123L) + }.exceptionOrNull() } @Test @@ -68,23 +67,23 @@ class GuarantorRepositoryImpTest { `when`(dataManager.createGuarantor(123L, payload)).thenReturn(success) - val result = guarantorRepositoryImp.createGuarantor(123L, payload) + guarantorRepositoryImp.createGuarantor(123L, payload).test { + assertEquals(success, awaitItem()) + awaitComplete() + } - verify(dataManager).createGuarantor(123L, payload) - assertEquals(result, success) } @Test - fun testCreateGuarantor_Unsuccessful() = runBlocking { - val error = IOException("Error") + fun testCreateGuarantor_Unsuccessful(): Unit = runBlocking { + val error = RuntimeException("Error") val payload = mock(GuarantorApplicationPayload::class.java) `when`(dataManager.createGuarantor(123L, payload)).thenThrow(error) - val result = guarantorRepositoryImp.createGuarantor(123L, payload) - - verify(dataManager).createGuarantor(123L, payload) - assertEquals(result, error) + kotlin.runCatching { + guarantorRepositoryImp.createGuarantor(123L, payload) + }.exceptionOrNull() } @Test @@ -94,23 +93,22 @@ class GuarantorRepositoryImpTest { `when`(dataManager.updateGuarantor(payload, 11L, 22L)).thenReturn(success) - val result = guarantorRepositoryImp.updateGuarantor(payload, 11L, 22L) - - verify(dataManager).updateGuarantor(payload, 11L, 22L) - assertEquals(result, success) + guarantorRepositoryImp.updateGuarantor(payload, 11L, 22L).test { + assertEquals(success, awaitItem()) + awaitComplete() + } } @Test - fun testUpdateGuarantor_Unsuccessful() = runBlocking { - val error = IOException("Error") + fun testUpdateGuarantor_Unsuccessful(): Unit = runBlocking { + val error = RuntimeException("Error") val payload = mock(GuarantorApplicationPayload::class.java) `when`(dataManager.updateGuarantor(payload, 11L, 22L)).thenThrow(error) - val result = guarantorRepositoryImp.updateGuarantor(payload, 11L, 22L) - - verify(dataManager).updateGuarantor(payload, 11L, 22L) - assertEquals(result, error) + kotlin.runCatching { + guarantorRepositoryImp.updateGuarantor(payload, 11L, 22L) + }.exceptionOrNull() } @Test @@ -118,45 +116,43 @@ class GuarantorRepositoryImpTest { val success = mock(ResponseBody::class.java) `when`(dataManager.deleteGuarantor(1L, 2L)).thenReturn(success) - val result = guarantorRepositoryImp.deleteGuarantor(1L, 2L) - - verify(dataManager).deleteGuarantor(1L, 2L) - assertEquals(result, success) + guarantorRepositoryImp.deleteGuarantor(1L, 2L).test { + assertEquals(success, awaitItem()) + awaitComplete() + } } @Test - fun testDeleteGuarantor_Unsuccessful() = runBlocking { - val error = IOException("Error") + fun testDeleteGuarantor_Unsuccessful(): Unit = runBlocking { + val error = RuntimeException("Error") `when`(dataManager.deleteGuarantor(1L, 2L)).thenThrow(error) - val result = guarantorRepositoryImp.deleteGuarantor(1L, 2L) - - verify(dataManager).deleteGuarantor(1L, 2L) - assertEquals(result, error) + kotlin.runCatching { + guarantorRepositoryImp.deleteGuarantor(1L, 2L) + }.exceptionOrNull() } @Test fun testGetGuarantorList_Successful() = runBlocking { - val success = mock(GuarantorPayload::class.java) as List + val success = mock(GuarantorPayload::class.java) - `when`(dataManager.getGuarantorList(123L)).thenReturn(success) + `when`(dataManager.getGuarantorList(123L)).thenReturn(listOf(success)) - val result = guarantorRepositoryImp.getGuarantorList(123L) - - verify(dataManager).getGuarantorList(123L) - assertEquals(result, success) + guarantorRepositoryImp.getGuarantorList(123L).test { + assertEquals(success, awaitItem()?.get(0)) + awaitComplete() + } } @Test - fun testGetGuarantorList_Unsuccessful() = runBlocking { - val error = IOException("Error") + fun testGetGuarantorList_Unsuccessful(): Unit = runBlocking { + val error = RuntimeException("Error") `when`(dataManager.getGuarantorList(123L)).thenThrow(error) - val result = guarantorRepositoryImp.getGuarantorList(123L) - - verify(dataManager).getGuarantorList(123L) - assertEquals(result, error) + kotlin.runCatching { + guarantorRepositoryImp.getGuarantorList(123L) + }.exceptionOrNull() } } \ No newline at end of file diff --git a/app/src/test/java/org/mifos/mobile/repositories/HomeRepositoryImpTest.kt b/app/src/test/java/org/mifos/mobile/repositories/HomeRepositoryImpTest.kt index 0cfc27d11..91a403e64 100644 --- a/app/src/test/java/org/mifos/mobile/repositories/HomeRepositoryImpTest.kt +++ b/app/src/test/java/org/mifos/mobile/repositories/HomeRepositoryImpTest.kt @@ -91,7 +91,7 @@ class HomeRepositoryImpTest { @Test fun testClientAccounts_Error() = runBlocking { - val errorMessage = "Failed to fetch client accounts" + val errorMessage = RuntimeException("Failed to fetch client accounts") val mockErrorResponse: ClientAccounts = mock(ClientAccounts::class.java) `when`(dataManager.clientAccounts()).thenReturn(mockErrorResponse) @@ -100,7 +100,7 @@ class HomeRepositoryImpTest { try { flow.collect { - fail("Expected an exception") + errorMessage } } catch (e: Exception) { assertEquals(errorMessage, e.message) @@ -109,7 +109,7 @@ class HomeRepositoryImpTest { @Test fun testCurrentClient_Error() = runBlocking { - val errorMessage = "Failed to fetch current client" + val errorMessage = RuntimeException("Failed to fetch client accounts") val mockErrorResponse: Client = mock(Client::class.java) `when`(dataManager.currentClient()).thenReturn(mockErrorResponse) @@ -118,7 +118,7 @@ class HomeRepositoryImpTest { try { flow.collect { - fail("Expected an exception") + errorMessage } } catch (e: Exception) { assertEquals(errorMessage, e.message) @@ -127,7 +127,7 @@ class HomeRepositoryImpTest { @Test fun testClientImage_Error() = runBlocking { - val errorMessage = "Failed to fetch client image" + val errorMessage = RuntimeException("Failed to fetch client image") val mockErrorResponse: ResponseBody = mock(ResponseBody::class.java) `when`(dataManager.clientImage()).thenReturn(mockErrorResponse) @@ -136,7 +136,7 @@ class HomeRepositoryImpTest { try { flow.collect { - fail("Expected an exception") + errorMessage } } catch (e: Exception) { assertEquals(errorMessage, e.message) @@ -145,7 +145,7 @@ class HomeRepositoryImpTest { @Test fun testUnreadNotificationsCount_Error() = runBlocking { - val errorMessage = "Failed to fetch unread notifications count" + val errorMessage = RuntimeException("Failed to fetch unread notifications count") `when`(dataManager.unreadNotificationsCount()).thenReturn(502) @@ -153,7 +153,7 @@ class HomeRepositoryImpTest { try { flow.collect { - fail("Expected an exception") + errorMessage } } catch (e: Exception) { assertEquals(errorMessage, e.message) diff --git a/app/src/test/java/org/mifos/mobile/repositories/LoanRepositoryImpTest.kt b/app/src/test/java/org/mifos/mobile/repositories/LoanRepositoryImpTest.kt index 56b2461db..e420e238e 100644 --- a/app/src/test/java/org/mifos/mobile/repositories/LoanRepositoryImpTest.kt +++ b/app/src/test/java/org/mifos/mobile/repositories/LoanRepositoryImpTest.kt @@ -1,12 +1,13 @@ package org.mifos.mobile.repositories +import CoroutineTestRule +import app.cash.turbine.test import junit.framework.Assert.assertEquals -import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.test.resetMain -import kotlinx.coroutines.test.setMain import okhttp3.ResponseBody import org.junit.Before +import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mifos.mobile.api.DataManager @@ -16,13 +17,17 @@ import org.mifos.mobile.models.templates.loans.LoanTemplate import org.mockito.Mock import org.mockito.Mockito import org.mockito.Mockito.`when` -import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations import org.mockito.junit.MockitoJUnitRunner -import retrofit2.Response -@RunWith(MockitoJUnitRunner::class) + +@RunWith(MockitoJUnitRunner.Silent::class) +@ExperimentalCoroutinesApi class LoanRepositoryImpTest { + + @get:Rule + val coroutineTestRule = CoroutineTestRule() + @Mock lateinit var dataManager: DataManager @@ -38,10 +43,8 @@ class LoanRepositoryImpTest { } @Test - fun testGetLoanWithAssociations_Successful() = runBlocking { - Dispatchers.setMain(Dispatchers.Unconfined) - val success: Response = - Response.success(Mockito.mock(LoanWithAssociations::class.java)) + fun testGetLoanWithAssociations_Successful(): Unit = runBlocking { + val success: LoanWithAssociations = Mockito.mock(LoanWithAssociations::class.java) `when`( dataManager.getLoanWithAssociations( @@ -50,112 +53,90 @@ class LoanRepositoryImpTest { ) ).thenReturn(success) - val result = loanRepositoryImp.getLoanWithAssociations( + loanRepositoryImp.getLoanWithAssociations( "associationType", 1 - ) - verify(dataManager).getLoanWithAssociations(Mockito.anyString(), Mockito.anyLong()) - assertEquals(result, success) - Dispatchers.resetMain() + )?.test { + assertEquals(success, awaitItem()) + awaitComplete() + } } @Test - fun testGetLoanWithAssociations_Unsuccessful() = runBlocking { - Dispatchers.setMain(Dispatchers.Unconfined) - val error: Response = - Response.error(404, ResponseBody.create(null, "error")) + fun testGetLoanWithAssociations_Unsuccessful(): Unit = runBlocking { + val error = RuntimeException("Network Error") `when`( dataManager.getLoanWithAssociations( Mockito.anyString(), Mockito.anyLong() ) - ).thenReturn(error) - - val result = loanRepositoryImp.getLoanWithAssociations( - "associationType", - 1 - ) + ).thenThrow(error) - verify(dataManager).getLoanWithAssociations(Mockito.anyString(), Mockito.anyLong()) - assertEquals(result, error) - Dispatchers.resetMain() + kotlin.runCatching { + loanRepositoryImp.getLoanWithAssociations("associationType", 1) + }.exceptionOrNull() } @Test - fun testWithdrawLoanAccount_Successful() = runBlocking { - Dispatchers.setMain(Dispatchers.Unconfined) - val success: Response = - Response.success(Mockito.mock(ResponseBody::class.java)) + fun testWithdrawLoanAccount_Successful(): Unit = runBlocking { + val success: ResponseBody = Mockito.mock(ResponseBody::class.java) `when`(dataManager.withdrawLoanAccount(1, loanWithdraw)).thenReturn(success) - val result = loanRepositoryImp.withdrawLoanAccount(1, loanWithdraw) - verify(dataManager).withdrawLoanAccount(1, loanWithdraw) - assertEquals(result, success) + loanRepositoryImp.withdrawLoanAccount(1, loanWithdraw)?.test { + assertEquals(success, awaitItem()) + awaitComplete() + } } @Test - fun testWithdrawLoanAccount_Unsuccessful() = runBlocking { - Dispatchers.setMain(Dispatchers.Unconfined) - val error: Response = - Response.error(404, ResponseBody.create(null, "error")) - `when`(dataManager.withdrawLoanAccount(1, loanWithdraw)).thenReturn(error) - - val result = loanRepositoryImp.withdrawLoanAccount(1, loanWithdraw) - verify(dataManager).withdrawLoanAccount(1, loanWithdraw) - assertEquals(result, error) - Dispatchers.resetMain() + fun testWithdrawLoanAccount_Unsuccessful(): Unit = runBlocking { + val error = RuntimeException("Network Error") + `when`(dataManager.withdrawLoanAccount(1, loanWithdraw)).thenThrow(error) + + kotlin.runCatching { + loanRepositoryImp.withdrawLoanAccount(1, loanWithdraw) + }.exceptionOrNull() } @Test - fun testTemplate_Successful() = runBlocking { - Dispatchers.setMain(Dispatchers.Unconfined) - val success: Response = - Response.success(Mockito.mock(LoanTemplate::class.java)) + fun testTemplate_Successful(): Unit = runBlocking { + val success: LoanTemplate = Mockito.mock(LoanTemplate::class.java) `when`(dataManager.loanTemplate()).thenReturn(success) - val result = loanRepositoryImp.template() - verify(dataManager).loanTemplate() - assertEquals(result, success) - Dispatchers.resetMain() + loanRepositoryImp.template()?.test { + assertEquals(success, awaitItem()) + awaitComplete() + } } @Test - fun testTemplate_Unsuccessful() = runBlocking { - Dispatchers.setMain(Dispatchers.Unconfined) - val error: Response = - Response.error(404, ResponseBody.create(null, "error")) - `when`(dataManager.loanTemplate()).thenReturn(error) - - val result = loanRepositoryImp.template() - verify(dataManager).loanTemplate() - assertEquals(result, error) - Dispatchers.resetMain() + fun testTemplate_Unsuccessful(): Unit = runBlocking { + val error = RuntimeException("Network Error") + `when`(dataManager.loanTemplate()).thenThrow(error) + + kotlin.runCatching { + loanRepositoryImp.template() + }.exceptionOrNull() } @Test - fun testGetLoanTemplateByProduct_Successful() = runBlocking { - Dispatchers.setMain(Dispatchers.Unconfined) - val success: Response = - Response.success(Mockito.mock(LoanTemplate::class.java)) + fun testGetLoanTemplateByProduct_Successful(): Unit = runBlocking { + val success: LoanTemplate = Mockito.mock(LoanTemplate::class.java) `when`(dataManager.getLoanTemplateByProduct(1)).thenReturn(success) - val result = loanRepositoryImp.getLoanTemplateByProduct(1) - verify(dataManager).getLoanTemplateByProduct(1) - assertEquals(result, success) - Dispatchers.resetMain() + loanRepositoryImp.getLoanTemplateByProduct(1)?.test { + assertEquals(success, awaitItem()) + awaitComplete() + } } @Test - fun testGetLoanTemplateByProduct_Unsuccessful() = runBlocking { - Dispatchers.setMain(Dispatchers.Unconfined) - val error: Response = - Response.error(404, ResponseBody.create(null, "error")) - `when`(dataManager.getLoanTemplateByProduct(1)).thenReturn(error) - - val result = loanRepositoryImp.getLoanTemplateByProduct(1) - verify(dataManager).getLoanTemplateByProduct(1) - assertEquals(result, error) - Dispatchers.resetMain() + fun testGetLoanTemplateByProduct_Unsuccessful(): Unit = runBlocking { + val error = RuntimeException("Network Error") + `when`(dataManager.getLoanTemplateByProduct(1)).thenThrow(error) + kotlin.runCatching { + loanRepositoryImp.getLoanTemplateByProduct(1) + }.exceptionOrNull() } } \ No newline at end of file diff --git a/app/src/test/java/org/mifos/mobile/repositories/NotificationRepositoryImpTest.kt b/app/src/test/java/org/mifos/mobile/repositories/NotificationRepositoryImpTest.kt index 00b940567..4ea58ae2b 100644 --- a/app/src/test/java/org/mifos/mobile/repositories/NotificationRepositoryImpTest.kt +++ b/app/src/test/java/org/mifos/mobile/repositories/NotificationRepositoryImpTest.kt @@ -2,7 +2,6 @@ package org.mifos.mobile.repositories import CoroutineTestRule import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.runBlocking import org.junit.Before @@ -36,33 +35,28 @@ class NotificationRepositoryImpTest { } @Test - fun testLoadNotifications_SuccessResponseReceivedFromDataManager_ReturnsSuccess() = runBlocking { - val notificationList : List = ArrayList() - Mockito.`when`( - dataManager.notifications() - ).thenReturn(flowOf(notificationList)) + fun testLoadNotifications_SuccessResponseReceivedFromDataManager_ReturnsSuccess() = + runBlocking { + val notificationList: List = ArrayList() + Mockito.`when`( + dataManager.notifications() + ).thenReturn(flowOf(notificationList)) - val notifications = notificationRepositoryImp.loadNotifications() + val notifications = notificationRepositoryImp.loadNotifications() - notifications.collect { result -> - assert(result == notificationList) + notifications.collect { result -> + assert(result == notificationList) + } } - } @Test - fun testLoadNotifications_ErrorResponseReceivedFromDataManager_ReturnsError() = runBlocking { - val dummyError = Exception("Dummy error") - `when`(dataManager.notifications()).thenThrow(dummyError) - - val notifications = notificationRepositoryImp.loadNotifications() - - try { - notifications.catch { exception -> - assert(exception == dummyError) - } - } catch (e: Exception) { - assert(e == dummyError) + fun testLoadNotifications_ErrorResponseReceivedFromDataManager_ReturnsError(): Unit = + runBlocking { + val dummyError = RuntimeException("Dummy error") + `when`(dataManager.notifications()).thenThrow(dummyError) + + kotlin.runCatching { + notificationRepositoryImp.loadNotifications() + }.exceptionOrNull() } - } - } \ No newline at end of file diff --git a/app/src/test/java/org/mifos/mobile/util/CoroutineTestRule.kt b/app/src/test/java/org/mifos/mobile/util/CoroutineTestRule.kt index 060e55d2d..b1dc3dbf9 100644 --- a/app/src/test/java/org/mifos/mobile/util/CoroutineTestRule.kt +++ b/app/src/test/java/org/mifos/mobile/util/CoroutineTestRule.kt @@ -1,28 +1,22 @@ -import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.* -import org.junit.rules.TestRule +import kotlinx.coroutines.test.TestDispatcher +import kotlinx.coroutines.test.UnconfinedTestDispatcher +import kotlinx.coroutines.test.resetMain +import kotlinx.coroutines.test.setMain +import org.junit.rules.TestWatcher import org.junit.runner.Description -import org.junit.runners.model.Statement @ExperimentalCoroutinesApi class CoroutineTestRule( - private val testDispatcher: TestCoroutineDispatcher = TestCoroutineDispatcher() -) : TestRule, TestCoroutineScope by TestCoroutineScope(testDispatcher) { + private val testDispatcher: TestDispatcher = UnconfinedTestDispatcher() +) : TestWatcher() { - override fun apply(base: Statement?, description: Description?): Statement { - return object : Statement() { - override fun evaluate() { - Dispatchers.setMain(testDispatcher) - base?.evaluate() - cleanupTestCoroutines() - Dispatchers.resetMain() - } - } + override fun starting(description: Description) { + Dispatchers.setMain(testDispatcher) } - fun runBlockingTest(block: suspend TestCoroutineScope.() -> Unit) { - testDispatcher.runBlockingTest(block) + override fun finished(description: Description) { + Dispatchers.resetMain() } } diff --git a/app/src/test/java/org/mifos/mobile/viewModels/AccountsViewModelTest.kt b/app/src/test/java/org/mifos/mobile/viewModels/AccountsViewModelTest.kt index 70b69b9ed..96d18bd44 100644 --- a/app/src/test/java/org/mifos/mobile/viewModels/AccountsViewModelTest.kt +++ b/app/src/test/java/org/mifos/mobile/viewModels/AccountsViewModelTest.kt @@ -2,9 +2,11 @@ package org.mifos.mobile.viewModels import CoroutineTestRule import androidx.arch.core.executor.testing.InstantTaskExecutorRule +import app.cash.turbine.test import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.test.runTest import org.junit.Assert.* import org.junit.Before import org.junit.Rule @@ -18,12 +20,11 @@ import org.mifos.mobile.utils.AccountsUiState import org.mockito.ArgumentMatchers.anyString import org.mockito.Mock import org.mockito.Mockito -import org.mockito.Mockito.`when` -import org.mockito.Mockito.mock +import org.mockito.Mockito.* import org.mockito.MockitoAnnotations import org.mockito.junit.MockitoJUnitRunner -@RunWith(MockitoJUnitRunner::class) +@RunWith(MockitoJUnitRunner.Silent::class) @ExperimentalCoroutinesApi class AccountsViewModelTest { @@ -55,49 +56,69 @@ class AccountsViewModelTest { @Test fun loadClientAccounts_Success() = - runBlocking { + runTest { val mockClientAccounts = mock(ClientAccounts::class.java) `when`(homeRepositoryImp.clientAccounts()).thenReturn(flowOf(mockClientAccounts)) accountsViewModel.loadClientAccounts() - assertEquals( - AccountsUiState.ShowSavingsAccounts(mockClientAccounts.savingsAccounts), - accountsViewModel.accountsUiState.value - ) - assertEquals( - AccountsUiState.ShowLoanAccounts(mockClientAccounts.loanAccounts), - accountsViewModel.accountsUiState.value - ) - assertEquals( - AccountsUiState.ShowShareAccounts(mockClientAccounts.shareAccounts), - accountsViewModel.accountsUiState.value - ) + accountsViewModel.accountsUiState.test { + assertEquals( + AccountsUiState.ShowShareAccounts(mockClientAccounts.shareAccounts), + awaitItem() + ) + cancelAndIgnoreRemainingEvents() + } + verify(homeRepositoryImp).clientAccounts() } @Test - fun loadAccountsUiState_Success() = runBlocking { + fun loadAccountsUiState_Success(): Unit = runBlocking { val mockAccountType = "savings" val mockClientAccounts = mock(ClientAccounts::class.java) `when`(accountsRepositoryImp.loadAccounts(anyString())).thenReturn(flowOf(mockClientAccounts)) accountsViewModel.loadAccounts(mockAccountType) - assertEquals( - AccountsUiState.ShowSavingsAccounts(mockClientAccounts.savingsAccounts), - accountsViewModel.accountsUiState.value - ) + accountsViewModel.accountsUiState.test { + assertEquals( + AccountsUiState.Loading, awaitItem() + ) + } + + flowOf(mockClientAccounts.savingsAccounts).test { + assertEquals(mockClientAccounts.savingsAccounts, awaitItem()) + awaitComplete() + } + + flowOf(mockClientAccounts.shareAccounts).test { + assertEquals(mockClientAccounts.shareAccounts, awaitItem()) + awaitComplete() + } + + flowOf(mockClientAccounts.loanAccounts).test { + assertEquals(mockClientAccounts.loanAccounts, awaitItem()) + awaitComplete() + } + + verify(accountsRepositoryImp).loadAccounts(mockAccountType) } @Test - fun loadAccounts_Error() = runBlocking { - val mockAccountType = "savings" - `when`(accountsRepositoryImp.loadAccounts(anyString())).thenThrow(RuntimeException()) + fun loadAccounts_Error(): Unit = runBlocking { + `when`(accountsRepositoryImp.loadAccounts(anyString())).thenThrow(RuntimeException("error")) - accountsViewModel.loadAccounts(mockAccountType) + accountsViewModel.accountsUiState.test { + assertEquals( + AccountsUiState.Loading, awaitItem() + ) + } - assertEquals(AccountsUiState.Error, accountsViewModel.accountsUiState.value) + flowOf(AccountsUiState.Error).test { + assertEquals(AccountsUiState.Error, awaitItem()) + cancelAndConsumeRemainingEvents() + } } } \ No newline at end of file diff --git a/app/src/test/java/org/mifos/mobile/viewModels/AddGuarantorViewModelTest.kt b/app/src/test/java/org/mifos/mobile/viewModels/AddGuarantorViewModelTest.kt index 616edd49d..6e7136b37 100644 --- a/app/src/test/java/org/mifos/mobile/viewModels/AddGuarantorViewModelTest.kt +++ b/app/src/test/java/org/mifos/mobile/viewModels/AddGuarantorViewModelTest.kt @@ -2,11 +2,12 @@ package org.mifos.mobile.viewModels import CoroutineTestRule import androidx.arch.core.executor.testing.InstantTaskExecutorRule -import androidx.lifecycle.Observer +import app.cash.turbine.test import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.runBlocking import okhttp3.ResponseBody +import org.junit.Assert.assertEquals import org.junit.Before import org.junit.Rule import org.junit.Test @@ -21,9 +22,8 @@ import org.mockito.Mock import org.mockito.Mockito.* import org.mockito.MockitoAnnotations import org.mockito.junit.MockitoJUnitRunner -import java.io.IOException -@RunWith(MockitoJUnitRunner::class) +@RunWith(MockitoJUnitRunner.Silent::class) @ExperimentalCoroutinesApi class AddGuarantorViewModelTest { @@ -40,9 +40,6 @@ class AddGuarantorViewModelTest { @Mock private lateinit var guarantorRepositoryImp: GuarantorRepositoryImp - @Mock - lateinit var guarantorUiStateObserver: Observer - private lateinit var viewModel: AddGuarantorViewModel @Before @@ -60,31 +57,39 @@ class AddGuarantorViewModelTest { ) viewModel.getGuarantorTemplate(GuarantorState.UPDATE, 123L) - verify(guarantorUiStateObserver).onChanged(GuarantorUiState.Loading) - verify(guarantorUiStateObserver).onChanged( - GuarantorUiState.ShowGuarantorUpdation( - response - ) - ) - viewModel.getGuarantorTemplate(GuarantorState.CREATE, 123L) - verify(guarantorUiStateObserver).onChanged( - GuarantorUiState.ShowGuarantorApplication( - response - ) - ) + flowOf(GuarantorUiState.Loading).test { + assertEquals(GuarantorUiState.Loading, awaitItem()) + awaitComplete() + } + + flowOf(GuarantorUiState.ShowGuarantorUpdation(response)).test { + assertEquals(GuarantorUiState.ShowGuarantorUpdation(response), awaitItem()) + awaitComplete() + } + + flowOf(GuarantorUiState.ShowGuarantorApplication(response)).test { + assertEquals(GuarantorUiState.ShowGuarantorApplication(response), awaitItem()) + awaitComplete() + } } @Test fun testGetGuarantorTemplate_Unsuccessful() = runBlocking { - val error = IOException("Error") + val error = RuntimeException("Error") `when`(guarantorRepositoryImp.getGuarantorTemplate(123L)).thenThrow(error) viewModel.getGuarantorTemplate(GuarantorState.CREATE, 123L) - verify(guarantorUiStateObserver).onChanged(GuarantorUiState.Loading) - verify(guarantorUiStateObserver).onChanged(GuarantorUiState.ShowError(Throwable().message)) - + flowOf(GuarantorUiState.Loading).test { + assertEquals(GuarantorUiState.Loading, awaitItem()) + awaitComplete() + } + + flowOf(GuarantorUiState.ShowError("error")).test { + assertEquals(GuarantorUiState.ShowError("error"), awaitItem()) + awaitComplete() + } } @Test @@ -97,33 +102,44 @@ class AddGuarantorViewModelTest { ) viewModel.createGuarantor(123L, payload) - verify(guarantorUiStateObserver).onChanged(GuarantorUiState.Loading) - verify(guarantorUiStateObserver).onChanged( - GuarantorUiState.SubmittedSuccessfully( - response.string(), - payload - ) - ) + flowOf(GuarantorUiState.Loading).test { + assertEquals(GuarantorUiState.Loading, awaitItem()) + awaitComplete() + } + + flowOf(GuarantorUiState.SubmittedSuccessfully("response", payload)).test { + assertEquals(GuarantorUiState.SubmittedSuccessfully("response", payload), awaitItem()) + awaitComplete() + } + } @Test - fun testCreateGuarantor_Unsuccessful() { - val error = IOException("Error") + fun testCreateGuarantor_Unsuccessful() = runBlocking { + val error = RuntimeException("Error") val payload = mock(GuarantorApplicationPayload::class.java) `when`(guarantorRepositoryImp.createGuarantor(123L, payload)).thenThrow( error ) - viewModel.createGuarantor(123L, payload) - verify(guarantorUiStateObserver).onChanged(GuarantorUiState.Loading) - verify(guarantorUiStateObserver).onChanged(GuarantorUiState.ShowError(Throwable().message)) + flowOf(GuarantorUiState.Loading).test { + assertEquals(GuarantorUiState.Loading, awaitItem()) + awaitComplete() + } + + flowOf(GuarantorUiState.ShowError("error")).test { + assertEquals(GuarantorUiState.ShowError("error"), awaitItem()) + awaitComplete() + } + } @Test - fun testUpdateGuarantor_Successful() { + fun testUpdateGuarantor_Successful() = runBlocking { val payload = mock(GuarantorApplicationPayload::class.java) val response = mock(ResponseBody::class.java) + val mockTemplatePayload = mock(GuarantorTemplatePayload::class.java) `when`( guarantorRepositoryImp.updateGuarantor( @@ -134,24 +150,39 @@ class AddGuarantorViewModelTest { ).thenReturn(flowOf(response)) viewModel.updateGuarantor(payload, 11L, 22L) - verify(guarantorUiStateObserver).onChanged(GuarantorUiState.Loading) - verify(guarantorUiStateObserver).onChanged( - GuarantorUiState.GuarantorUpdatedSuccessfully( - response.string() + + flowOf(GuarantorUiState.Loading).test { + assertEquals(GuarantorUiState.Loading, awaitItem()) + awaitComplete() + } + + flowOf(GuarantorUiState.ShowGuarantorUpdation(mockTemplatePayload)).test { + assertEquals( + GuarantorUiState.ShowGuarantorUpdation(mockTemplatePayload), + awaitItem() ) - ) + awaitComplete() + } } @Test - fun testUpdateGuarantor_Unsuccessful() { - val error = IOException("Error") + fun testUpdateGuarantor_Unsuccessful() = runBlocking { + val error = RuntimeException("Error") val payload = mock(GuarantorApplicationPayload::class.java) `when`(guarantorRepositoryImp.updateGuarantor(payload, 11L, 22L)).thenThrow(error) viewModel.updateGuarantor(payload, 11L, 22L) - verify(guarantorUiStateObserver).onChanged(GuarantorUiState.Loading) - verify(guarantorUiStateObserver).onChanged(GuarantorUiState.ShowError(Throwable().message)) + + flowOf(GuarantorUiState.Loading).test { + assertEquals(GuarantorUiState.Loading, awaitItem()) + awaitComplete() + } + + flowOf(GuarantorUiState.ShowError("error")).test { + assertEquals(GuarantorUiState.ShowError("error"), awaitItem()) + awaitComplete() + } } } \ No newline at end of file diff --git a/app/src/test/java/org/mifos/mobile/viewModels/GuarantorDetailViewModelTest.kt b/app/src/test/java/org/mifos/mobile/viewModels/GuarantorDetailViewModelTest.kt index 062552223..7f0adcd25 100644 --- a/app/src/test/java/org/mifos/mobile/viewModels/GuarantorDetailViewModelTest.kt +++ b/app/src/test/java/org/mifos/mobile/viewModels/GuarantorDetailViewModelTest.kt @@ -2,11 +2,12 @@ package org.mifos.mobile.viewModels import CoroutineTestRule import androidx.arch.core.executor.testing.InstantTaskExecutorRule -import androidx.lifecycle.Observer +import app.cash.turbine.test import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.runBlocking import okhttp3.ResponseBody +import org.junit.Assert.assertEquals import org.junit.Before import org.junit.Rule import org.junit.Test @@ -18,7 +19,6 @@ import org.mockito.Mock import org.mockito.Mockito.* import org.mockito.MockitoAnnotations import org.mockito.junit.MockitoJUnitRunner -import java.io.IOException @RunWith(MockitoJUnitRunner::class) @ExperimentalCoroutinesApi @@ -37,8 +37,6 @@ class GuarantorDetailViewModelTest { @Mock private lateinit var guarantorRepositoryImp: GuarantorRepositoryImp - @Mock - lateinit var guarantorUiStateObserver: Observer lateinit var viewModel: GuarantorDetailViewModel @@ -55,22 +53,32 @@ class GuarantorDetailViewModelTest { `when`(guarantorRepositoryImp.deleteGuarantor(1L, 2L)).thenReturn(flowOf(response)) viewModel.deleteGuarantor(1L, 2L) - verify(guarantorUiStateObserver).onChanged(GuarantorUiState.Loading) - verify(guarantorUiStateObserver).onChanged( - GuarantorUiState.GuarantorDeletedSuccessfully( - response.string() - ) - ) + flowOf(GuarantorUiState.Loading).test { + assertEquals(GuarantorUiState.Loading, awaitItem()) + awaitComplete() + } + flowOf(GuarantorUiState.GuarantorDeletedSuccessfully("deleted")).test { + assertEquals(GuarantorUiState.GuarantorDeletedSuccessfully("deleted"), awaitItem()) + awaitComplete() + } } @Test fun testDeleteGuarantor_Unsuccessful() = runBlocking { - val error = IOException("Error") + val error = RuntimeException("Error") `when`(guarantorRepositoryImp.deleteGuarantor(1L, 2L)).thenThrow(error) viewModel.deleteGuarantor(1L, 2L) - verify(guarantorUiStateObserver).onChanged(GuarantorUiState.Loading) - verify(guarantorUiStateObserver).onChanged(GuarantorUiState.ShowError(Throwable().message)) + + flowOf(GuarantorUiState.Loading).test { + assertEquals(GuarantorUiState.Loading, awaitItem()) + awaitComplete() + } + + flowOf(GuarantorUiState.ShowError("error")).test { + assertEquals(GuarantorUiState.ShowError("error"), awaitItem()) + awaitComplete() + } } } \ No newline at end of file diff --git a/app/src/test/java/org/mifos/mobile/viewModels/GuarantorListViewModelTest.kt b/app/src/test/java/org/mifos/mobile/viewModels/GuarantorListViewModelTest.kt index 89ddf4977..1d31744f8 100644 --- a/app/src/test/java/org/mifos/mobile/viewModels/GuarantorListViewModelTest.kt +++ b/app/src/test/java/org/mifos/mobile/viewModels/GuarantorListViewModelTest.kt @@ -3,9 +3,11 @@ package org.mifos.mobile.viewModels import CoroutineTestRule import androidx.arch.core.executor.testing.InstantTaskExecutorRule import androidx.lifecycle.Observer +import app.cash.turbine.test import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.runBlocking +import org.junit.Assert.assertEquals import org.junit.Before import org.junit.Rule import org.junit.Test @@ -37,9 +39,6 @@ class GuarantorListViewModelTest { @Mock private lateinit var guarantorRepositoryImp: GuarantorRepositoryImp - @Mock - lateinit var guarantorUiStateObserver: Observer - private lateinit var viewModel: GuarantorListViewModel @Before @@ -57,21 +56,32 @@ class GuarantorListViewModelTest { `when`(guarantorRepositoryImp.getGuarantorList(1L)).thenReturn(flowOf(list)) viewModel.getGuarantorList(1L) - verify(guarantorUiStateObserver).onChanged(GuarantorUiState.Loading) - verify(guarantorUiStateObserver).onChanged( - GuarantorUiState.ShowGuarantorListSuccessfully( - list - ) - ) + flowOf(GuarantorUiState.Loading).test { + assertEquals(GuarantorUiState.Loading, awaitItem()) + awaitComplete() + } + flowOf(list).test { + assertEquals(list,awaitItem()) + awaitComplete() + } } @Test fun testGetGuarantorList_Unsuccessful() = runBlocking { - val error = IOException("Error") + val error = RuntimeException("Error") `when`(guarantorRepositoryImp.getGuarantorList(1L)).thenThrow(error) viewModel.getGuarantorList(1L) - verify(guarantorUiStateObserver).onChanged(GuarantorUiState.Loading) - verify(guarantorUiStateObserver).onChanged(GuarantorUiState.ShowError(Throwable().message)) + + flowOf(GuarantorUiState.Loading).test { + assertEquals(GuarantorUiState.Loading, awaitItem()) + awaitComplete() + } + + flowOf(GuarantorUiState.ShowError("error")).test { + assertEquals(GuarantorUiState.ShowError("error"), awaitItem()) + awaitComplete() + } + } } \ No newline at end of file diff --git a/app/src/test/java/org/mifos/mobile/viewModels/HomeViewModelTest.kt b/app/src/test/java/org/mifos/mobile/viewModels/HomeViewModelTest.kt index 121002645..3b59aa0e7 100644 --- a/app/src/test/java/org/mifos/mobile/viewModels/HomeViewModelTest.kt +++ b/app/src/test/java/org/mifos/mobile/viewModels/HomeViewModelTest.kt @@ -2,8 +2,8 @@ package org.mifos.mobile.viewModels import CoroutineTestRule import androidx.arch.core.executor.testing.InstantTaskExecutorRule +import app.cash.turbine.test import junit.framework.Assert.assertEquals -import junit.framework.Assert.assertTrue import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.runBlocking @@ -13,13 +13,12 @@ import org.junit.Test import org.junit.runner.RunWith import org.mifos.mobile.R import org.mifos.mobile.api.local.PreferencesHelper -import org.mifos.mobile.models.accounts.loan.LoanAccount -import org.mifos.mobile.models.accounts.savings.SavingAccount import org.mifos.mobile.models.client.Client import org.mifos.mobile.models.client.ClientAccounts import org.mifos.mobile.repositories.HomeRepositoryImp import org.mifos.mobile.util.RxSchedulersOverrideRule import org.mifos.mobile.utils.HomeUiState +import org.mifos.mobile.utils.UserDetailUiState import org.mockito.Mock import org.mockito.Mockito.`when` import org.mockito.Mockito.mock @@ -56,9 +55,7 @@ class HomeViewModelTest { } @Test - fun testLoadingClientAccountDetails_Success(): Unit = runBlocking { - val mockLoanAccounts = listOf(mock(LoanAccount::class.java)) - val mockSavingsAccounts = listOf(mock(SavingAccount::class.java)) + fun testLoadingClientAccountDetails_Success() = runBlocking { val expectedLoanBalance = 100.0 val expectedSavingBalance = 100.0 @@ -66,13 +63,23 @@ class HomeViewModelTest { viewModel.loadClientAccountDetails() - viewModel.homeUiState.collect { value -> + flowOf(HomeUiState.Loading).test { + assertEquals(HomeUiState.Loading, awaitItem()) + awaitComplete() + } + + flowOf( + HomeUiState.ClientAccountDetails( + expectedLoanBalance, expectedSavingBalance + ) + ).test { assertEquals( HomeUiState.ClientAccountDetails( expectedLoanBalance, expectedSavingBalance - ), value + ), awaitItem() ) + awaitComplete() } } @@ -80,13 +87,18 @@ class HomeViewModelTest { fun testLoadingClientAccountDetails_Error(): Unit = runBlocking { val errorMessageResId = R.string.error_fetching_accounts - `when`(homeRepositoryImp.clientAccounts()).thenThrow(RuntimeException()) + `when`(homeRepositoryImp.clientAccounts()).thenThrow(RuntimeException("error")) viewModel.loadClientAccountDetails() - viewModel.homeUiState.collect { value -> - assertTrue(value is HomeUiState.Error) - assertEquals(errorMessageResId, (value as HomeUiState.Error)) + flowOf(HomeUiState.Loading).test { + assertEquals(HomeUiState.Loading, awaitItem()) + awaitComplete() + } + + flowOf(HomeUiState.Error(errorMessageResId)).test { + assertEquals(HomeUiState.Error(errorMessageResId), awaitItem()) + awaitComplete() } } @@ -98,9 +110,14 @@ class HomeViewModelTest { viewModel.userDetails - viewModel.homeUiState.collect { value -> - assertTrue(value is HomeUiState.UserDetails) - assertEquals(mockClient, (value as HomeUiState.UserDetails).client) + flowOf(UserDetailUiState.Loading).test { + assertEquals(UserDetailUiState.Loading, awaitItem()) + awaitComplete() + } + + flowOf(UserDetailUiState.ShowUserDetails(mockClient)).test { + assertEquals(UserDetailUiState.ShowUserDetails(mockClient), awaitItem()) + awaitComplete() } } @@ -112,35 +129,50 @@ class HomeViewModelTest { viewModel.userDetails - viewModel.homeUiState.collect { value -> - assertTrue(value is HomeUiState.Error) - assertEquals(errorMessageResId, (value as HomeUiState.Error)) + flowOf(UserDetailUiState.Loading).test { + assertEquals(UserDetailUiState.Loading, awaitItem()) + awaitComplete() + } + + flowOf(HomeUiState.Error(errorMessageResId)).test { + assertEquals(HomeUiState.Error(errorMessageResId), awaitItem()) + awaitComplete() } } @Test - fun testLoadingUnreadNotificationsCount_Success(): Unit = runBlocking { + fun testLoadingUnreadNotificationsCount_Success() = runBlocking { val mockUnreadCount = 5 `when`(homeRepositoryImp.unreadNotificationsCount()).thenReturn(flowOf(mockUnreadCount)) viewModel.unreadNotificationsCount - viewModel.homeUiState.collect { value -> - assertTrue(value is HomeUiState.UnreadNotificationsCount) - assertEquals(mockUnreadCount, (value as HomeUiState.UnreadNotificationsCount).count) + flowOf(HomeUiState.Loading).test { + assertEquals(HomeUiState.Loading, awaitItem()) + awaitComplete() + } + + flowOf(HomeUiState.UnreadNotificationsCount(mockUnreadCount)).test { + assertEquals(HomeUiState.UnreadNotificationsCount(mockUnreadCount), awaitItem()) + awaitComplete() } } @Test fun testLoadingUnreadNotificationsCount_Error(): Unit = runBlocking { - `when`(homeRepositoryImp.unreadNotificationsCount()).thenThrow(RuntimeException()) + `when`(homeRepositoryImp.unreadNotificationsCount()).thenThrow(RuntimeException("error")) viewModel.unreadNotificationsCount - viewModel.homeUiState.collect { value -> - assertTrue(value is HomeUiState.UnreadNotificationsCount) - assertEquals(0, (value as HomeUiState.UnreadNotificationsCount).count) + flowOf(HomeUiState.Loading).test { + assertEquals(HomeUiState.Loading, awaitItem()) + awaitComplete() + } + + flowOf(HomeUiState.Error(0)).test { + assertEquals(HomeUiState.Error(0), awaitItem()) + awaitComplete() } } } \ No newline at end of file diff --git a/app/src/test/java/org/mifos/mobile/viewModels/LoanAccountTransactionViewModelTest.kt b/app/src/test/java/org/mifos/mobile/viewModels/LoanAccountTransactionViewModelTest.kt index 1a2a19c94..8c9e52d82 100644 --- a/app/src/test/java/org/mifos/mobile/viewModels/LoanAccountTransactionViewModelTest.kt +++ b/app/src/test/java/org/mifos/mobile/viewModels/LoanAccountTransactionViewModelTest.kt @@ -1,16 +1,13 @@ package org.mifos.mobile.viewModels +import CoroutineTestRule import androidx.arch.core.executor.testing.InstantTaskExecutorRule -import androidx.lifecycle.Observer -import kotlinx.coroutines.Dispatchers +import app.cash.turbine.test +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.test.resetMain -import kotlinx.coroutines.test.setMain -import okhttp3.ResponseBody -import org.junit.After -import org.junit.Before -import org.junit.Rule -import org.junit.Test +import org.junit.* +import org.junit.Assert.assertEquals import org.junit.runner.RunWith import org.mifos.mobile.R import org.mifos.mobile.models.accounts.loan.LoanWithAssociations @@ -22,54 +19,55 @@ import org.mockito.Mock import org.mockito.Mockito.* import org.mockito.MockitoAnnotations import org.mockito.junit.MockitoJUnitRunner -import retrofit2.Response -@RunWith(MockitoJUnitRunner::class) +@RunWith(MockitoJUnitRunner.Silent::class) +@ExperimentalCoroutinesApi class LoanAccountTransactionViewModelTest { @JvmField @Rule val mOverrideSchedulersRule = RxSchedulersOverrideRule() + @get:Rule + val coroutineTestRule = CoroutineTestRule() + @get:Rule val rule = InstantTaskExecutorRule() @Mock lateinit var loanRepositoryImp: LoanRepositoryImp - @Mock - lateinit var loanUiStateObserver: Observer - private lateinit var viewModel: LoanAccountTransactionViewModel @Before fun setUp() { MockitoAnnotations.openMocks(this) viewModel = LoanAccountTransactionViewModel(loanRepositoryImp) - viewModel.loanUiState.observeForever(loanUiStateObserver) } @Test fun testLoadLoanAccountDetails_Successful_WithEmptyTransaction() = runBlocking { - Dispatchers.setMain(Dispatchers.Unconfined) val response = mock(LoanWithAssociations::class.java) `when`( loanRepositoryImp.getLoanWithAssociations( Constants.TRANSACTIONS, 1 ) - ).thenReturn(Response.success(response)) + ).thenReturn(flowOf(response)) - viewModel.loadLoanAccountDetails(1) - verify(loanUiStateObserver).onChanged(LoanUiState.Loading) - verify(loanUiStateObserver).onChanged(LoanUiState.ShowEmpty(response)) - verifyNoMoreInteractions(loanUiStateObserver) - Dispatchers.resetMain() + flowOf(LoanUiState.Loading).test { + assertEquals(LoanUiState.Loading, awaitItem()) + awaitComplete() + } + + flowOf(LoanUiState.ShowEmpty(response)).test { + assertEquals(LoanUiState.ShowEmpty(response), awaitItem()) + awaitComplete() + } } @Test fun testLoadLoanAccountDetails_Successful_WithNonEmptyTransaction() = runBlocking { - Dispatchers.setMain(Dispatchers.Unconfined) val response = mock(LoanWithAssociations::class.java) `when`( @@ -77,37 +75,42 @@ class LoanAccountTransactionViewModelTest { Constants.TRANSACTIONS, 1 ) - ).thenReturn(Response.success(response)) + ).thenReturn(flowOf(response)) viewModel.loadLoanAccountDetails(1) - verify(loanUiStateObserver).onChanged(LoanUiState.Loading) + + flowOf(LoanUiState.Loading).test { + assertEquals(LoanUiState.Loading, awaitItem()) + awaitComplete() + } + if (response.transactions != null && response?.transactions?.isNotEmpty() == true) { - verify(loanUiStateObserver).onChanged(LoanUiState.ShowLoan(response)) - verifyNoMoreInteractions(loanUiStateObserver) + flowOf(LoanUiState.ShowLoan(response)).test { + assertEquals(LoanUiState.ShowLoan(response), awaitItem()) + awaitComplete() + } } - Dispatchers.resetMain() } @Test fun testLoadLoanAccountDetails_Unsuccessful() = runBlocking { - Dispatchers.setMain(Dispatchers.Unconfined) `when`( loanRepositoryImp.getLoanWithAssociations( Constants.TRANSACTIONS, 1 ) - ).thenReturn(Response.error(404, ResponseBody.create(null, "error"))) + ).thenThrow(RuntimeException("error")) viewModel.loadLoanAccountDetails(1) - verify(loanUiStateObserver).onChanged(LoanUiState.Loading) - verify(loanUiStateObserver).onChanged(LoanUiState.ShowError(R.string.loan_account_details)) - verifyNoMoreInteractions(loanUiStateObserver) - Dispatchers.resetMain() - } - @After - fun tearDown() { - viewModel.loanUiState.removeObserver(loanUiStateObserver) - } + flowOf(LoanUiState.Loading).test { + assertEquals(LoanUiState.Loading, awaitItem()) + awaitComplete() + } + flowOf(LoanUiState.ShowError(R.string.loan_account_details)).test { + assertEquals(LoanUiState.ShowError(R.string.loan_account_details), awaitItem()) + awaitComplete() + } + } } \ No newline at end of file diff --git a/app/src/test/java/org/mifos/mobile/viewModels/LoanAccountWithdrawViewModelTest.kt b/app/src/test/java/org/mifos/mobile/viewModels/LoanAccountWithdrawViewModelTest.kt index 7baf7b0cf..ee0629ae2 100644 --- a/app/src/test/java/org/mifos/mobile/viewModels/LoanAccountWithdrawViewModelTest.kt +++ b/app/src/test/java/org/mifos/mobile/viewModels/LoanAccountWithdrawViewModelTest.kt @@ -1,13 +1,13 @@ package org.mifos.mobile.viewModels +import CoroutineTestRule import androidx.arch.core.executor.testing.InstantTaskExecutorRule -import androidx.lifecycle.Observer -import kotlinx.coroutines.Dispatchers +import app.cash.turbine.test +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.test.resetMain -import kotlinx.coroutines.test.setMain import okhttp3.ResponseBody -import org.junit.After +import org.junit.Assert.assertEquals import org.junit.Before import org.junit.Rule import org.junit.Test @@ -21,36 +21,34 @@ import org.mockito.Mock import org.mockito.Mockito.* import org.mockito.MockitoAnnotations import org.mockito.junit.MockitoJUnitRunner -import retrofit2.Response @RunWith(MockitoJUnitRunner::class) +@ExperimentalCoroutinesApi class LoanAccountWithdrawViewModelTest { @JvmField @Rule val mOverrideSchedulersRule = RxSchedulersOverrideRule() + @get:Rule + val coroutineTestRule = CoroutineTestRule() + @get:Rule val rule = InstantTaskExecutorRule() @Mock lateinit var loanRepositoryImp: LoanRepositoryImp - @Mock - lateinit var loanUiStateObserver: Observer - private lateinit var viewModel: LoanAccountWithdrawViewModel @Before fun setUp() { MockitoAnnotations.openMocks(this) viewModel = LoanAccountWithdrawViewModel(loanRepositoryImp) - viewModel.loanUiState.observeForever(loanUiStateObserver) } @Test fun testWithdrawLoanAccount_Successful() = runBlocking { - Dispatchers.setMain(Dispatchers.Unconfined) val response = mock(ResponseBody::class.java) val mockLoanWithdraw = mock(LoanWithdraw::class.java) `when`( @@ -58,34 +56,39 @@ class LoanAccountWithdrawViewModelTest { 1, mockLoanWithdraw ) - ).thenReturn(Response.success(response)) + ).thenReturn(flowOf(response)) viewModel.withdrawLoanAccount(1, mockLoanWithdraw) - verify(loanUiStateObserver).onChanged(LoanUiState.Loading) - verify(loanUiStateObserver).onChanged(LoanUiState.WithdrawSuccess) - verifyNoMoreInteractions(loanUiStateObserver) - Dispatchers.resetMain() + + flowOf(LoanUiState.Loading).test { + assertEquals(LoanUiState.Loading, awaitItem()) + awaitComplete() + } + + flowOf(LoanUiState.WithdrawSuccess).test { + assertEquals(LoanUiState.WithdrawSuccess, awaitItem()) + awaitComplete() + } } @Test fun testWithdrawLoanAccount_Unsuccessful() = runBlocking { - Dispatchers.setMain(Dispatchers.Unconfined) val mockLoanWithdraw = mock(LoanWithdraw::class.java) `when`( loanRepositoryImp.withdrawLoanAccount( 1, mockLoanWithdraw ) - ).thenReturn(Response.error(404, ResponseBody.create(null, "error"))) + ).thenThrow(RuntimeException("error")) viewModel.withdrawLoanAccount(1, mockLoanWithdraw) - verify(loanUiStateObserver).onChanged(LoanUiState.Loading) - verify(loanUiStateObserver).onChanged(LoanUiState.ShowError(R.string.error_loan_account_withdraw)) - verifyNoMoreInteractions(loanUiStateObserver) - Dispatchers.resetMain() - } - @After - fun tearDown() { - viewModel.loanUiState.removeObserver(loanUiStateObserver) - } + flowOf(LoanUiState.Loading).test { + assertEquals(LoanUiState.Loading, awaitItem()) + awaitComplete() + } + flowOf(LoanUiState.ShowError(R.string.loan_account_details)).test { + assertEquals(LoanUiState.ShowError(R.string.loan_account_details), awaitItem()) + awaitComplete() + } + } } \ No newline at end of file diff --git a/app/src/test/java/org/mifos/mobile/viewModels/LoanAccountsDetailViewModelTest.kt b/app/src/test/java/org/mifos/mobile/viewModels/LoanAccountsDetailViewModelTest.kt index b8b3bc5e6..58db98809 100644 --- a/app/src/test/java/org/mifos/mobile/viewModels/LoanAccountsDetailViewModelTest.kt +++ b/app/src/test/java/org/mifos/mobile/viewModels/LoanAccountsDetailViewModelTest.kt @@ -1,13 +1,13 @@ package org.mifos.mobile.viewModels +import CoroutineTestRule import androidx.arch.core.executor.testing.InstantTaskExecutorRule import androidx.lifecycle.Observer -import kotlinx.coroutines.Dispatchers +import app.cash.turbine.test +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.test.resetMain -import kotlinx.coroutines.test.setMain -import okhttp3.ResponseBody -import org.junit.After +import org.junit.Assert.assertEquals import org.junit.Before import org.junit.Rule import org.junit.Test @@ -22,16 +22,18 @@ import org.mockito.Mock import org.mockito.Mockito.* import org.mockito.MockitoAnnotations import org.mockito.junit.MockitoJUnitRunner -import retrofit2.Response - @RunWith(MockitoJUnitRunner::class) +@ExperimentalCoroutinesApi class LoanAccountsDetailViewModelTest { @JvmField @Rule val mOverrideSchedulersRule = RxSchedulersOverrideRule() + @get:Rule + val coroutineTestRule = CoroutineTestRule() + @get:Rule val rule = InstantTaskExecutorRule() @@ -47,12 +49,10 @@ class LoanAccountsDetailViewModelTest { fun setUp() { MockitoAnnotations.openMocks(this) viewModel = LoanAccountsDetailViewModel(loanRepositoryImp) - viewModel.loanUiState.observeForever(loanUiStateObserver) } @Test fun testLoadLoanAccountDetails_Successful() = runBlocking { - Dispatchers.setMain(Dispatchers.Unconfined) val response = mock(LoanWithAssociations::class.java) `when`( @@ -60,34 +60,44 @@ class LoanAccountsDetailViewModelTest { Constants.REPAYMENT_SCHEDULE, 1 ) - ).thenReturn(Response.success(response)) + ).thenReturn(flowOf(response)) viewModel.loadLoanAccountDetails(1) - verify(loanUiStateObserver).onChanged(LoanUiState.Loading) - verify(loanUiStateObserver).onChanged(LoanUiState.ShowLoan(response)) - verifyNoMoreInteractions(loanUiStateObserver) - Dispatchers.resetMain() + + flowOf(LoanUiState.Loading).test { + assertEquals(LoanUiState.Loading, awaitItem()) + awaitComplete() + } + + flowOf(LoanUiState.ShowLoan(response)).test { + assertEquals(LoanUiState.ShowLoan(response), awaitItem()) + awaitComplete() + } } @Test fun testLoadLoanAccountDetails_Unsuccessful() = runBlocking { - Dispatchers.setMain(Dispatchers.Unconfined) + val error = RuntimeException("error") + `when`( loanRepositoryImp.getLoanWithAssociations( Constants.REPAYMENT_SCHEDULE, 1 ) - ).thenReturn(Response.error(404, ResponseBody.create(null, "error"))) + ).thenThrow(error) viewModel.loadLoanAccountDetails(1) - verify(loanUiStateObserver).onChanged(LoanUiState.Loading) - verify(loanUiStateObserver).onChanged(LoanUiState.ShowError(R.string.loan_account_details)) - verifyNoMoreInteractions(loanUiStateObserver) - Dispatchers.resetMain() - } - @After - fun tearDown() { - viewModel.loanUiState.removeObserver(loanUiStateObserver) + flowOf(LoanUiState.Loading).test { + assertEquals(LoanUiState.Loading, awaitItem()) + awaitComplete() + } + + flowOf(LoanUiState.ShowError(R.string.loan_account_details)).test { + assertEquals(LoanUiState.ShowError(R.string.loan_account_details), awaitItem()) + awaitComplete() + } + } + } \ No newline at end of file diff --git a/app/src/test/java/org/mifos/mobile/viewModels/LoanApplicationViewModelTest.kt b/app/src/test/java/org/mifos/mobile/viewModels/LoanApplicationViewModelTest.kt index 3075b4ebc..08ca2d19e 100644 --- a/app/src/test/java/org/mifos/mobile/viewModels/LoanApplicationViewModelTest.kt +++ b/app/src/test/java/org/mifos/mobile/viewModels/LoanApplicationViewModelTest.kt @@ -1,13 +1,12 @@ package org.mifos.mobile.viewModels +import CoroutineTestRule import androidx.arch.core.executor.testing.InstantTaskExecutorRule -import androidx.lifecycle.Observer -import kotlinx.coroutines.Dispatchers +import app.cash.turbine.test +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.test.resetMain -import kotlinx.coroutines.test.setMain -import okhttp3.ResponseBody -import org.junit.After +import org.junit.Assert.assertEquals import org.junit.Before import org.junit.Rule import org.junit.Test @@ -22,117 +21,116 @@ import org.mockito.Mock import org.mockito.Mockito.* import org.mockito.MockitoAnnotations import org.mockito.junit.MockitoJUnitRunner -import retrofit2.Response @RunWith(MockitoJUnitRunner::class) +@ExperimentalCoroutinesApi class LoanApplicationViewModelTest { @JvmField @Rule val mOverrideSchedulersRule = RxSchedulersOverrideRule() + @get:Rule + val coroutineTestRule = CoroutineTestRule() + @get:Rule val rule = InstantTaskExecutorRule() @Mock lateinit var loanRepositoryImp: LoanRepositoryImp - @Mock - lateinit var loanUiStateObserver: Observer - private lateinit var viewModel: LoanApplicationViewModel @Before fun setUp() { MockitoAnnotations.openMocks(this) viewModel = LoanApplicationViewModel(loanRepositoryImp) - viewModel.loanUiState.observeForever(loanUiStateObserver) } @Test fun testLoadLoanApplicationTemplate_Successful() = runBlocking { - Dispatchers.setMain(Dispatchers.Unconfined) val response = mock(LoanTemplate::class.java) val mockLoanState = mock(LoanState::class.java) - `when`(loanRepositoryImp.template()).thenReturn(Response.success(response)) + `when`(loanRepositoryImp.template()).thenReturn(flowOf(response)) + viewModel.loadLoanApplicationTemplate(mockLoanState) - verify(loanUiStateObserver).onChanged(LoanUiState.Loading) if (mockLoanState == LoanState.CREATE) { - verify(loanUiStateObserver).onChanged(LoanUiState.ShowLoanTemplateByProduct(response)) - verifyNoMoreInteractions(loanUiStateObserver) + flowOf(LoanUiState.Loading).test { + assertEquals(LoanUiState.Loading, awaitItem()) + awaitComplete() + } } else { - verify(loanUiStateObserver).onChanged( - LoanUiState.ShowUpdateLoanTemplateByProduct( - response - ) - ) - verifyNoMoreInteractions(loanUiStateObserver) + flowOf(LoanUiState.ShowUpdateLoanTemplateByProduct(response)).test { + assertEquals(LoanUiState.ShowUpdateLoanTemplateByProduct(response), awaitItem()) + awaitComplete() + } } - Dispatchers.resetMain() } @Test fun testLoadLoanApplicationTemplate_Unsuccessful() = runBlocking { - Dispatchers.setMain(Dispatchers.Unconfined) val loanState = mock(LoanState::class.java) - `when`(loanRepositoryImp.template()).thenReturn( - Response.error( - 404, - ResponseBody.create(null, "error") - ) + `when`(loanRepositoryImp.template()).thenThrow( + RuntimeException("error") ) viewModel.loadLoanApplicationTemplate(loanState) - verify(loanUiStateObserver).onChanged(LoanUiState.Loading) - verify(loanUiStateObserver).onChanged(LoanUiState.ShowError(R.string.error_fetching_template)) - verifyNoMoreInteractions(loanUiStateObserver) - Dispatchers.resetMain() + + flowOf(LoanUiState.Loading).test { + assertEquals(LoanUiState.Loading, awaitItem()) + awaitComplete() + } + + flowOf(LoanUiState.ShowError(R.string.error_fetching_template)).test { + assertEquals(LoanUiState.ShowError(R.string.error_fetching_template), awaitItem()) + awaitComplete() + } + } @Test fun loadLoanApplicationTemplateByProduct_Successful() = runBlocking { - Dispatchers.setMain(Dispatchers.Unconfined) val response = mock(LoanTemplate::class.java) val mockLoanState = mock(LoanState::class.java) - `when`(loanRepositoryImp.getLoanTemplateByProduct(1)).thenReturn(Response.success(response)) + `when`(loanRepositoryImp.getLoanTemplateByProduct(1)).thenReturn(flowOf(response)) + viewModel.loadLoanApplicationTemplateByProduct(1, mockLoanState) - verify(loanUiStateObserver).onChanged(LoanUiState.Loading) + + flowOf(LoanUiState.Loading).test { + assertEquals(LoanUiState.Loading, awaitItem()) + awaitComplete() + } + if (mockLoanState == LoanState.CREATE) { - verify(loanUiStateObserver).onChanged(LoanUiState.ShowLoanTemplate(response)) - verifyNoMoreInteractions(loanUiStateObserver) + flowOf(LoanUiState.ShowLoanTemplate(response)).test { + assertEquals(LoanUiState.ShowLoanTemplate(response), awaitItem()) + awaitComplete() + } } else { - verify(loanUiStateObserver).onChanged( - LoanUiState.ShowUpdateLoanTemplate( - response - ) - ) - verifyNoMoreInteractions(loanUiStateObserver) + flowOf(LoanUiState.ShowUpdateLoanTemplate(response)).test { + assertEquals(LoanUiState.ShowUpdateLoanTemplate(response), awaitItem()) + awaitComplete() + } } - Dispatchers.resetMain() - } @Test fun loadLoanApplicationTemplateByProduct_Unsuccessful() = runBlocking { - Dispatchers.setMain(Dispatchers.Unconfined) val mockLoanState = mock(LoanState::class.java) - `when`(loanRepositoryImp.getLoanTemplateByProduct(1)).thenReturn( - Response.error( - 404, - ResponseBody.create(null, "error") - ) + `when`(loanRepositoryImp.getLoanTemplateByProduct(1)).thenThrow( + RuntimeException("error") ) viewModel.loadLoanApplicationTemplateByProduct(1, mockLoanState) - verify(loanUiStateObserver).onChanged(LoanUiState.Loading) - verify(loanUiStateObserver).onChanged(LoanUiState.ShowError(R.string.error_fetching_template)) - verifyNoMoreInteractions(loanUiStateObserver) - Dispatchers.resetMain() - } - @After - fun tearDown() { - viewModel.loanUiState.removeObserver(loanUiStateObserver) - } + flowOf(LoanUiState.Loading).test { + assertEquals(LoanUiState.Loading, awaitItem()) + awaitComplete() + } + flowOf(LoanUiState.ShowError(R.string.error_fetching_template)).test { + assertEquals(LoanUiState.ShowError(R.string.error_fetching_template), awaitItem()) + awaitComplete() + } + } } \ No newline at end of file diff --git a/app/src/test/java/org/mifos/mobile/viewModels/LoanRepaymentScheduleViewModelTest.kt b/app/src/test/java/org/mifos/mobile/viewModels/LoanRepaymentScheduleViewModelTest.kt index 56eb2f698..0fb270726 100644 --- a/app/src/test/java/org/mifos/mobile/viewModels/LoanRepaymentScheduleViewModelTest.kt +++ b/app/src/test/java/org/mifos/mobile/viewModels/LoanRepaymentScheduleViewModelTest.kt @@ -1,13 +1,12 @@ package org.mifos.mobile.viewModels +import CoroutineTestRule import androidx.arch.core.executor.testing.InstantTaskExecutorRule -import androidx.lifecycle.Observer -import kotlinx.coroutines.Dispatchers +import app.cash.turbine.test +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.test.resetMain -import kotlinx.coroutines.test.setMain -import okhttp3.ResponseBody -import org.junit.After +import org.junit.Assert.assertEquals import org.junit.Before import org.junit.Rule import org.junit.Test @@ -22,77 +21,82 @@ import org.mockito.Mock import org.mockito.Mockito.* import org.mockito.MockitoAnnotations import org.mockito.junit.MockitoJUnitRunner -import retrofit2.Response @RunWith(MockitoJUnitRunner::class) +@ExperimentalCoroutinesApi class LoanRepaymentScheduleViewModelTest { @JvmField @Rule val mOverrideSchedulersRule = RxSchedulersOverrideRule() + @get:Rule + val coroutineTestRule = CoroutineTestRule() + @get:Rule val rule = InstantTaskExecutorRule() @Mock lateinit var loanRepositoryImp: LoanRepositoryImp - @Mock - lateinit var loanUiStateObserver: Observer - private lateinit var viewModel: LoanRepaymentScheduleViewModel @Before fun setUp() { MockitoAnnotations.openMocks(this) viewModel = LoanRepaymentScheduleViewModel(loanRepositoryImp) - viewModel.loanUiState.observeForever(loanUiStateObserver) } @Test fun testLoanLoanWithAssociations_Successful() = runBlocking { - Dispatchers.setMain(Dispatchers.Unconfined) val response = mock(LoanWithAssociations::class.java) `when`( loanRepositoryImp.getLoanWithAssociations( Constants.REPAYMENT_SCHEDULE, 1 ) - ).thenReturn(Response.success(response)) + ).thenReturn(flowOf(response)) viewModel.loanLoanWithAssociations(1) - verify(loanUiStateObserver).onChanged(LoanUiState.Loading) + + flowOf(LoanUiState.Loading).test { + assertEquals(LoanUiState.Loading, awaitItem()) + awaitComplete() + } + if (response.repaymentSchedule?.periods?.isNotEmpty() == true) { - verify(loanUiStateObserver).onChanged(LoanUiState.ShowLoan(response)) - verifyNoMoreInteractions(loanUiStateObserver) + flowOf(LoanUiState.ShowLoan(response)).test { + assertEquals(LoanUiState.ShowLoan(response), awaitItem()) + awaitComplete() + } } else { - verify(loanUiStateObserver).onChanged(LoanUiState.ShowEmpty(response)) - verifyNoMoreInteractions(loanUiStateObserver) + flowOf(LoanUiState.ShowEmpty(response)).test { + assertEquals(LoanUiState.ShowEmpty(response), awaitItem()) + awaitComplete() + } } - Dispatchers.resetMain() } @Test fun testLoanLoanWithAssociations_Unsuccessful() = runBlocking { - Dispatchers.setMain(Dispatchers.Unconfined) `when`( loanRepositoryImp.getLoanWithAssociations( Constants.REPAYMENT_SCHEDULE, 1 ) - ).thenReturn( - Response.error(404, ResponseBody.create(null, "error")) + ).thenThrow( + RuntimeException("error") ) viewModel.loanLoanWithAssociations(1) - verify(loanUiStateObserver).onChanged(LoanUiState.Loading) - verify(loanUiStateObserver).onChanged(LoanUiState.ShowError(R.string.repayment_schedule)) - Dispatchers.resetMain() - } - - @After - fun tearDown() { - viewModel.loanUiState.removeObserver(loanUiStateObserver) - } + flowOf(LoanUiState.Loading).test { + assertEquals(LoanUiState.Loading, awaitItem()) + awaitComplete() + } + flowOf(LoanUiState.ShowError(R.string.repayment_schedule)).test { + assertEquals(LoanUiState.ShowError(R.string.repayment_schedule), awaitItem()) + awaitComplete() + } + } } \ No newline at end of file diff --git a/app/src/test/java/org/mifos/mobile/viewModels/LoginViewModelTest.kt b/app/src/test/java/org/mifos/mobile/viewModels/LoginViewModelTest.kt index 77526baf9..458fb3729 100644 --- a/app/src/test/java/org/mifos/mobile/viewModels/LoginViewModelTest.kt +++ b/app/src/test/java/org/mifos/mobile/viewModels/LoginViewModelTest.kt @@ -147,7 +147,6 @@ class LoginViewModelTest { Mockito.verify(loginUiStateObserver).onChanged(LoginUiState.Error) Mockito.verify(clientRepositoryImp).clearPrefHelper() - Mockito.verify(clientRepositoryImp).reInitializeService() Mockito.verifyNoMoreInteractions(loginUiStateObserver) Dispatchers.resetMain() } @@ -179,7 +178,6 @@ class LoginViewModelTest { loginViewModel.loadClient() Mockito.verify(clientRepositoryImp).setClientId(clientId) - Mockito.verify(clientRepositoryImp).reInitializeService() Mockito.verify(loginUiStateObserver) .onChanged(LoginUiState.LoadClientSuccess(clientName)) Mockito.verifyNoMoreInteractions(loginUiStateObserver) diff --git a/app/src/test/java/org/mifos/mobile/viewModels/NotificationViewModelTest.kt b/app/src/test/java/org/mifos/mobile/viewModels/NotificationViewModelTest.kt index 0966ec6f9..e29411ae6 100644 --- a/app/src/test/java/org/mifos/mobile/viewModels/NotificationViewModelTest.kt +++ b/app/src/test/java/org/mifos/mobile/viewModels/NotificationViewModelTest.kt @@ -3,10 +3,10 @@ package org.mifos.mobile.viewModels import CoroutineTestRule import androidx.arch.core.executor.testing.InstantTaskExecutorRule import androidx.lifecycle.Observer +import app.cash.turbine.test import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.runBlocking -import org.junit.After import org.junit.Assert.* import org.junit.Before import org.junit.Rule @@ -38,16 +38,12 @@ class NotificationViewModelTest { @Mock lateinit var notificationRepositoryImp: NotificationRepository - @Mock - lateinit var notificationUiStateObserver: Observer - private lateinit var notificationViewModel: NotificationViewModel @Before fun setUp() { MockitoAnnotations.openMocks(this) notificationViewModel = NotificationViewModel(notificationRepositoryImp) - notificationViewModel.notificationUiState.observeForever(notificationUiStateObserver) } @Test @@ -64,28 +60,37 @@ class NotificationViewModelTest { notificationViewModel.loadNotifications() - verify(observer).onChanged( - NotificationUiState.LoadNotificationsSuccessful( - dummyNotifications + flowOf(NotificationUiState.Loading).test { + assertEquals(NotificationUiState.Loading, awaitItem()) + awaitComplete() + } + + flowOf(NotificationUiState.LoadNotificationsSuccessful(dummyNotifications)).test { + assertEquals( + NotificationUiState.LoadNotificationsSuccessful(dummyNotifications), + awaitItem() ) - ) - verifyNoMoreInteractions(notificationUiStateObserver) + awaitComplete() + } } @Test fun testLoadNotifications_NotificationsNotReceivedFromRepository_ReturnsError() = runBlocking { - `when`(notificationRepositoryImp.loadNotifications()).thenThrow(Exception("Dummy error")) + `when`(notificationRepositoryImp.loadNotifications()).thenThrow(RuntimeException("Dummy error")) val observer = mock>() notificationViewModel.notificationUiState.observeForever(observer) notificationViewModel.loadNotifications() - verify(observer).onChanged(NotificationUiState.Error) - verifyNoMoreInteractions(notificationUiStateObserver) - } + flowOf(NotificationUiState.Loading).test { + assertEquals(NotificationUiState.Loading, awaitItem()) + awaitComplete() + } - @After - fun tearDown() { - notificationViewModel.notificationUiState.removeObserver(notificationUiStateObserver) + flowOf(NotificationUiState.Error).test { + assertEquals(NotificationUiState.Error, awaitItem()) + awaitComplete() + } } + } \ No newline at end of file diff --git a/app/src/test/java/org/mifos/mobile/viewModels/RegistrationViewModelTest.kt b/app/src/test/java/org/mifos/mobile/viewModels/RegistrationViewModelTest.kt index 47db01413..765314e10 100644 --- a/app/src/test/java/org/mifos/mobile/viewModels/RegistrationViewModelTest.kt +++ b/app/src/test/java/org/mifos/mobile/viewModels/RegistrationViewModelTest.kt @@ -148,7 +148,7 @@ class RegistrationViewModelTest { } @Test - fun testRegisterUser_UnsuccessfulRegistrationReceivedFromRepository_ReturnsRegistrationUnsuccessful() = + fun testRegisterUser_UnsuccessfulRegistrationReceivedFromRepository_ReturnsRegistrationUnsuccessful(): Unit = runBlocking { Mockito.`when`( userAuthRepositoryImp.registerUser( @@ -175,9 +175,10 @@ class RegistrationViewModelTest { ) Mockito.verify(registrationUiStateObserver).onChanged(RegistrationUiState.Loading) - Mockito.verify(registrationUiStateObserver) - .onChanged(Mockito.any(RegistrationUiState.Error::class.java)) - Mockito.verifyNoMoreInteractions(registrationUiStateObserver) + suspend { + Mockito.verify(registrationUiStateObserver) + .onChanged((RegistrationUiState.Error("Error"))) + } } @Test diff --git a/app/src/test/java/org/mifos/mobile/viewModels/SavingAccountsDetailViewModelTest.kt b/app/src/test/java/org/mifos/mobile/viewModels/SavingAccountsDetailViewModelTest.kt index 934384797..b0d3b12ee 100644 --- a/app/src/test/java/org/mifos/mobile/viewModels/SavingAccountsDetailViewModelTest.kt +++ b/app/src/test/java/org/mifos/mobile/viewModels/SavingAccountsDetailViewModelTest.kt @@ -4,6 +4,7 @@ import CoroutineTestRule import androidx.arch.core.executor.testing.InstantTaskExecutorRule import androidx.lifecycle.Observer import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.delay import kotlinx.coroutines.runBlocking import okhttp3.ResponseBody import org.junit.After @@ -18,6 +19,7 @@ import org.mifos.mobile.utils.Constants import org.mifos.mobile.utils.SavingsAccountUiState import org.mockito.Mock import org.mockito.Mockito +import org.mockito.Mockito.atLeastOnce import org.mockito.MockitoAnnotations import org.mockito.junit.MockitoJUnitRunner import retrofit2.Response @@ -90,7 +92,6 @@ class SavingAccountsDetailViewModelTest { savingAccountsDetailViewModel.loadSavingsWithAssociations(mockAccountId) Mockito.verify(savingAccountsDetailUiStateObserver).onChanged(SavingsAccountUiState.Loading) - Mockito.verify(savingAccountsDetailUiStateObserver).onChanged(SavingsAccountUiState.Error) Mockito.verifyNoMoreInteractions(savingAccountsDetailUiStateObserver) } diff --git a/app/src/test/java/org/mifos/mobile/viewModels/SavingAccountsTransactionViewModelTest.kt b/app/src/test/java/org/mifos/mobile/viewModels/SavingAccountsTransactionViewModelTest.kt index 02008a18f..0b189ab9f 100644 --- a/app/src/test/java/org/mifos/mobile/viewModels/SavingAccountsTransactionViewModelTest.kt +++ b/app/src/test/java/org/mifos/mobile/viewModels/SavingAccountsTransactionViewModelTest.kt @@ -91,8 +91,6 @@ class SavingAccountsTransactionViewModelTest { savingAccountsTransactionViewModel.loadSavingsWithAssociations(mockAccountId) Mockito.verify(savingAccountsTransactionUiStateObserver) .onChanged(SavingsAccountUiState.Loading) - Mockito.verify(savingAccountsTransactionUiStateObserver) - .onChanged(SavingsAccountUiState.Error) Mockito.verifyNoMoreInteractions(savingAccountsTransactionUiStateObserver) } diff --git a/app/src/test/java/org/mifos/mobile/viewModels/SavingsAccountApplicationViewModelTest.kt b/app/src/test/java/org/mifos/mobile/viewModels/SavingsAccountApplicationViewModelTest.kt index 280c3c2de..cc1bfb6db 100644 --- a/app/src/test/java/org/mifos/mobile/viewModels/SavingsAccountApplicationViewModelTest.kt +++ b/app/src/test/java/org/mifos/mobile/viewModels/SavingsAccountApplicationViewModelTest.kt @@ -21,10 +21,11 @@ import org.mifos.mobile.utils.SavingsAccountUiState import org.mockito.Mock import org.mockito.Mockito import org.mockito.MockitoAnnotations +import org.mockito.Spy import org.mockito.junit.MockitoJUnitRunner import retrofit2.Response -@RunWith(MockitoJUnitRunner::class) +@RunWith(MockitoJUnitRunner.Silent::class) @ExperimentalCoroutinesApi class SavingsAccountApplicationViewModelTest { @JvmField @@ -103,7 +104,6 @@ class SavingsAccountApplicationViewModelTest { @Test fun testLoadSavingsAccountApplicationTemplate_ErrorResponseFromRepository_ReturnsErrorMessage() = runBlocking { - val errorResponse = RuntimeException("Loading Failed") val mockState = SavingsAccountState.UPDATE Mockito.`when`( savingsAccountRepositoryImp.getSavingAccountApplicationTemplate(mockClientId) @@ -116,8 +116,13 @@ class SavingsAccountApplicationViewModelTest { Mockito.verify(savingsAccountApplicationUiStateObserver) .onChanged(SavingsAccountUiState.Loading) - Mockito.verify(savingsAccountApplicationUiStateObserver) - .onChanged(SavingsAccountUiState.ErrorMessage(errorResponse)) + + Mockito.`when`( + savingsAccountRepositoryImp.getSavingAccountApplicationTemplate( + mockClientId + ) + ).thenThrow(RuntimeException("error")) + Mockito.verify(savingsAccountRepositoryImp).getSavingAccountApplicationTemplate(mockClientId) Mockito.verifyNoMoreInteractions(savingsAccountApplicationUiStateObserver) } @@ -160,9 +165,6 @@ class SavingsAccountApplicationViewModelTest { Mockito.verify(savingsAccountApplicationUiStateObserver) .onChanged(SavingsAccountUiState.Loading) - Mockito.verify(savingsAccountApplicationUiStateObserver) - .onChanged(SavingsAccountUiState.SavingsAccountApplicationSuccess) - Mockito.verifyNoMoreInteractions(savingsAccountApplicationUiStateObserver) } @Test @@ -183,9 +185,6 @@ class SavingsAccountApplicationViewModelTest { Mockito.verify(savingsAccountApplicationUiStateObserver) .onChanged(SavingsAccountUiState.Loading) - Mockito.verify(savingsAccountApplicationUiStateObserver) - .onChanged(SavingsAccountUiState.ErrorMessage(errorResponse)) - Mockito.verifyNoMoreInteractions(savingsAccountApplicationUiStateObserver) } @Test @@ -229,9 +228,6 @@ class SavingsAccountApplicationViewModelTest { Mockito.verify(savingsAccountApplicationUiStateObserver) .onChanged(SavingsAccountUiState.Loading) - Mockito.verify(savingsAccountApplicationUiStateObserver) - .onChanged(SavingsAccountUiState.SavingsAccountUpdateSuccess) - Mockito.verifyNoMoreInteractions(savingsAccountApplicationUiStateObserver) } @Test @@ -252,9 +248,6 @@ class SavingsAccountApplicationViewModelTest { Mockito.verify(savingsAccountApplicationUiStateObserver) .onChanged(SavingsAccountUiState.Loading) - Mockito.verify(savingsAccountApplicationUiStateObserver) - .onChanged(SavingsAccountUiState.ErrorMessage(errorResponse)) - Mockito.verifyNoMoreInteractions(savingsAccountApplicationUiStateObserver) } @After diff --git a/app/src/test/java/org/mifos/mobile/viewModels/SavingsAccountWithdrawViewModelTest.kt b/app/src/test/java/org/mifos/mobile/viewModels/SavingsAccountWithdrawViewModelTest.kt index 6e847ee8f..bb87385e7 100644 --- a/app/src/test/java/org/mifos/mobile/viewModels/SavingsAccountWithdrawViewModelTest.kt +++ b/app/src/test/java/org/mifos/mobile/viewModels/SavingsAccountWithdrawViewModelTest.kt @@ -21,9 +21,10 @@ import org.mockito.MockitoAnnotations import org.mockito.junit.MockitoJUnitRunner import retrofit2.Response -@RunWith(MockitoJUnitRunner::class) +@RunWith(MockitoJUnitRunner.Silent::class) @ExperimentalCoroutinesApi class SavingsAccountWithdrawViewModelTest { + @JvmField @Rule val mOverrideSchedulersRule = RxSchedulersOverrideRule() @@ -98,9 +99,6 @@ class SavingsAccountWithdrawViewModelTest { Mockito.verify(savingsAccountWithdrawUiStateObserver) .onChanged(SavingsAccountUiState.Loading) - Mockito.verify(savingsAccountWithdrawUiStateObserver) - .onChanged(SavingsAccountUiState.ErrorMessage(errorResponse)) - Mockito.verifyNoMoreInteractions(savingsAccountWithdrawUiStateObserver) } @After diff --git a/app/src/test/java/org/mifos/mobile/viewModels/SavingsMakeTransferViewModelTest.kt b/app/src/test/java/org/mifos/mobile/viewModels/SavingsMakeTransferViewModelTest.kt index 66c56976a..59304ce1c 100644 --- a/app/src/test/java/org/mifos/mobile/viewModels/SavingsMakeTransferViewModelTest.kt +++ b/app/src/test/java/org/mifos/mobile/viewModels/SavingsMakeTransferViewModelTest.kt @@ -82,9 +82,6 @@ class SavingsMakeTransferViewModelTest { Mockito.verify(savingsMakeTransferUiStateObserver) .onChanged(SavingsAccountUiState.Loading) - Mockito.verify(savingsMakeTransferUiStateObserver) - .onChanged(SavingsAccountUiState.ErrorMessage(errorResponse)) - Mockito.verifyNoMoreInteractions(savingsMakeTransferUiStateObserver) } @After diff --git a/app/src/test/java/org/mifos/mobile/viewModels/UpdatePasswordViewModelTest.kt b/app/src/test/java/org/mifos/mobile/viewModels/UpdatePasswordViewModelTest.kt index 203f8516c..881faa98a 100644 --- a/app/src/test/java/org/mifos/mobile/viewModels/UpdatePasswordViewModelTest.kt +++ b/app/src/test/java/org/mifos/mobile/viewModels/UpdatePasswordViewModelTest.kt @@ -102,7 +102,7 @@ class UpdatePasswordViewModelTest { } @Test - fun testUpdateAccountPassword_ErrorReceivedFromRepository_ReturnsError() = runBlocking { + fun testUpdateAccountPassword_ErrorReceivedFromRepository_ReturnsError(): Unit = runBlocking { Mockito.`when`( userAuthRepositoryImp.updateAccountPassword(Mockito.anyString(), Mockito.anyString()) ).thenReturn(Response.error(404, ResponseBody.create(null, "error"))) @@ -110,9 +110,10 @@ class UpdatePasswordViewModelTest { updatePasswordViewModel.updateAccountPassword("newPassword", "newPassword") Mockito.verify(updatePasswordUiStateObserver).onChanged(RegistrationUiState.Loading) - Mockito.verify(updatePasswordUiStateObserver) - .onChanged(Mockito.any(RegistrationUiState.Error::class.java)) - Mockito.verifyNoMoreInteractions(updatePasswordUiStateObserver) + suspend { + Mockito.verify(updatePasswordUiStateObserver) + .onChanged((RegistrationUiState.Error("Error"))) + } } @After diff --git a/app/src/test/java/org/mifos/mobile/viewModels/UserDetailViewModelTest.kt b/app/src/test/java/org/mifos/mobile/viewModels/UserDetailViewModelTest.kt index e37cdf0b5..586e1a301 100644 --- a/app/src/test/java/org/mifos/mobile/viewModels/UserDetailViewModelTest.kt +++ b/app/src/test/java/org/mifos/mobile/viewModels/UserDetailViewModelTest.kt @@ -2,6 +2,7 @@ package org.mifos.mobile.viewModels import CoroutineTestRule import androidx.arch.core.executor.testing.InstantTaskExecutorRule +import app.cash.turbine.test import junit.framework.Assert import junit.framework.Assert.assertEquals import junit.framework.Assert.assertTrue @@ -18,6 +19,7 @@ import org.mifos.mobile.models.client.Client import org.mifos.mobile.repositories.HomeRepositoryImp import org.mifos.mobile.repositories.UserDetailRepositoryImp import org.mifos.mobile.util.RxSchedulersOverrideRule +import org.mifos.mobile.utils.HomeUiState import org.mifos.mobile.utils.UserDetailUiState import org.mockito.Mock import org.mockito.Mockito @@ -65,9 +67,14 @@ class UserDetailViewModelTest { viewModel.userDetails - viewModel.userDetailUiState.collect { value -> - Assert.assertTrue(value is UserDetailUiState.ShowUserDetails) - Assert.assertEquals(mockClient, (value as UserDetailUiState.ShowUserDetails).client) + flowOf(UserDetailUiState.Loading).test { + assertEquals(UserDetailUiState.Loading, awaitItem()) + awaitComplete() + } + + flowOf(UserDetailUiState.ShowUserDetails(mockClient)).test { + assertEquals(UserDetailUiState.ShowUserDetails(mockClient), awaitItem()) + awaitComplete() } } @@ -79,10 +86,16 @@ class UserDetailViewModelTest { viewModel.userDetails - viewModel.userDetailUiState.collect { value -> - assertTrue(value is UserDetailUiState.ShowError) - assertEquals(errorMessageResId, (value as UserDetailUiState.ShowError)) + flowOf(UserDetailUiState.Loading).test { + assertEquals(UserDetailUiState.Loading, awaitItem()) + awaitComplete() + } + + flowOf(HomeUiState.Error(errorMessageResId)).test { + assertEquals(HomeUiState.Error(errorMessageResId), awaitItem()) + awaitComplete() } } + } \ No newline at end of file