From b6283340f203249f164306405d16c727455560be Mon Sep 17 00:00:00 2001 From: hmalik144 Date: Sun, 28 Aug 2022 00:56:07 +0100 Subject: [PATCH] Issue resolved with unit tests Took 1 hour 31 minutes --- .../h_mal/easycc/ui/widget/WidgetViewModel.kt | 2 +- .../repository/RepositoryNetworkTest.kt | 10 +- .../h_mal/easycc/ui/BaseViewModelTest.kt | 22 ++++ .../h_mal/easycc/ui/main/MainViewModelTest.kt | 114 +++++++----------- .../easycc/ui/widget/WidgetViewModelTest.kt | 31 +++-- 5 files changed, 91 insertions(+), 88 deletions(-) create mode 100644 app/src/test/java/com/appttude/h_mal/easycc/ui/BaseViewModelTest.kt diff --git a/app/src/main/java/com/appttude/h_mal/easycc/ui/widget/WidgetViewModel.kt b/app/src/main/java/com/appttude/h_mal/easycc/ui/widget/WidgetViewModel.kt index 64d4c46..60306a9 100644 --- a/app/src/main/java/com/appttude/h_mal/easycc/ui/widget/WidgetViewModel.kt +++ b/app/src/main/java/com/appttude/h_mal/easycc/ui/widget/WidgetViewModel.kt @@ -45,7 +45,7 @@ class WidgetViewModel @Inject constructor( return } if (rateIdFrom == rateIdTo) { - onError("Selected rates cannot be the same ${rateIdFrom}${rateIdTo}") + onError("Selected rates cannot be the same") return } onSuccess(Unit) diff --git a/app/src/test/java/com/appttude/h_mal/easycc/repository/RepositoryNetworkTest.kt b/app/src/test/java/com/appttude/h_mal/easycc/repository/RepositoryNetworkTest.kt index 3a67271..fdce986 100644 --- a/app/src/test/java/com/appttude/h_mal/easycc/repository/RepositoryNetworkTest.kt +++ b/app/src/test/java/com/appttude/h_mal/easycc/repository/RepositoryNetworkTest.kt @@ -1,11 +1,13 @@ package com.appttude.h_mal.easycc.repository +import com.appttude.h_mal.easycc.data.network.SafeApiRequest import com.appttude.h_mal.easycc.data.network.api.BackupCurrencyApi import com.appttude.h_mal.easycc.data.network.api.CurrencyApi import com.appttude.h_mal.easycc.data.network.response.ResponseObject import com.appttude.h_mal.easycc.data.prefs.PreferenceProvider import com.appttude.h_mal.easycc.data.repository.Repository import com.appttude.h_mal.easycc.data.repository.RepositoryImpl +import com.appttude.h_mal.easycc.models.CurrencyModel import com.appttude.h_mal.easycc.utils.convertPairsListToString import kotlinx.coroutines.runBlocking import okhttp3.ResponseBody @@ -22,7 +24,7 @@ import java.io.IOException import kotlin.test.assertFailsWith -class RepositoryNetworkTest { +class RepositoryNetworkTest : SafeApiRequest() { lateinit var repository: Repository @@ -49,15 +51,16 @@ class RepositoryNetworkTest { //create a successful retrofit response val mockCurrencyResponse = mock(ResponseObject::class.java) val re = Response.success(mockCurrencyResponse) + val currencyModel = mock(CurrencyModel::class.java) //WHEN - loginApiRequest to return a successful response val currencyPair = convertPairsListToString(s1, s2) Mockito.`when`(api.getCurrencyRate(currencyPair)).thenReturn(re) + Mockito.`when`(responseUnwrap { api.getCurrencyRate(currencyPair) }.getCurrencyModel()).thenReturn(currencyModel) //THEN - the unwrapped login response contains the correct values val currencyResponse = repository.getDataFromApi(s1, s2) - assertNotNull(currencyResponse) - assertEquals(currencyResponse, mockCurrencyResponse) + assertEquals(currencyResponse, currencyModel) } @Test @@ -74,6 +77,7 @@ class RepositoryNetworkTest { //WHEN val currencyPair = convertPairsListToString(s1, s2) Mockito.`when`(api.getCurrencyRate(currencyPair)).thenAnswer { re } + Mockito.`when`(apiBackup.getCurrencyRate(s1, s2)).thenAnswer { re } //THEN - assert exception is not null val ioExceptionReturned = assertFailsWith { diff --git a/app/src/test/java/com/appttude/h_mal/easycc/ui/BaseViewModelTest.kt b/app/src/test/java/com/appttude/h_mal/easycc/ui/BaseViewModelTest.kt new file mode 100644 index 0000000..7e9f54b --- /dev/null +++ b/app/src/test/java/com/appttude/h_mal/easycc/ui/BaseViewModelTest.kt @@ -0,0 +1,22 @@ +package com.appttude.h_mal.easycc.ui + +import androidx.lifecycle.MutableLiveData +import com.appttude.h_mal.easycc.utils.ViewState + +abstract class BaseViewModelTest { + + abstract val viewModel: V? + + open fun setUp() { + viewModel?.uiState?.observeForever { + when (it) { + is ViewState.HasStarted -> Unit + is ViewState.HasData<*> -> dataPost.postValue(it.data.getContentIfNotHandled()) + is ViewState.HasError -> errorPost.postValue(it.error.getContentIfNotHandled()) + } + } + } + + var dataPost: MutableLiveData = MutableLiveData() + var errorPost: MutableLiveData = MutableLiveData() +} \ No newline at end of file diff --git a/app/src/test/java/com/appttude/h_mal/easycc/ui/main/MainViewModelTest.kt b/app/src/test/java/com/appttude/h_mal/easycc/ui/main/MainViewModelTest.kt index 49e7b98..c4d90a7 100644 --- a/app/src/test/java/com/appttude/h_mal/easycc/ui/main/MainViewModelTest.kt +++ b/app/src/test/java/com/appttude/h_mal/easycc/ui/main/MainViewModelTest.kt @@ -1,17 +1,17 @@ package com.appttude.h_mal.easycc.ui.main import android.os.Bundle -import android.util.Log import androidx.arch.core.executor.testing.InstantTaskExecutorRule -import com.appttude.h_mal.easycc.data.network.response.ResponseObject import com.appttude.h_mal.easycc.data.repository.Repository -import com.appttude.h_mal.easycc.helper.CurrencyDataHelper +import com.appttude.h_mal.easycc.models.CurrencyModel +import com.appttude.h_mal.easycc.ui.BaseViewModelTest import com.appttude.h_mal.easycc.utils.MainCoroutineRule import com.appttude.h_mal.easycc.utils.observeOnce import com.nhaarman.mockitokotlin2.doAnswer import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.runBlocking -import org.junit.Assert.* +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNull import org.junit.Before import org.junit.Rule import org.junit.Test @@ -22,7 +22,7 @@ import org.mockito.MockitoAnnotations import java.io.IOException @OptIn(ExperimentalCoroutinesApi::class) -class MainViewModelTest { +class MainViewModelTest : BaseViewModelTest(){ // Run tasks synchronously @get:Rule @@ -31,27 +31,27 @@ class MainViewModelTest { @get:Rule var mainCoroutineRule = MainCoroutineRule() - lateinit var viewModel: MainViewModel + override lateinit var viewModel: MainViewModel @Mock lateinit var repository: Repository - @Mock - lateinit var helper: CurrencyDataHelper + private val currencyOne = "AUD - Australian Dollar" + private val currencyTwo = "GBP - British Pound" @Before - fun setUp() { + override fun setUp() { MockitoAnnotations.initMocks(this) - viewModel = MainViewModel(helper, repository) + viewModel = MainViewModel(repository) + + super.setUp() } @Test fun initiate_validBundleValues_successResponse() = runBlocking { //GIVEN - val currencyOne = "AUD - Australian Dollar" - val currencyTwo = "GBP - British Pound" val bundle = mock(Bundle()::class.java) - val responseObject = mock(ResponseObject::class.java) + val responseObject = mock(CurrencyModel::class.java) //WHEN Mockito.`when`(bundle.getString("parse_1")).thenReturn(currencyOne) @@ -61,59 +61,50 @@ class MainViewModelTest { //THEN viewModel.initiate(bundle) - viewModel.operationStartedListener.observeOnce { - assertEquals(true, it) - } - viewModel.operationFinishedListener.observeOnce { - assertEquals(true, it.first) - assertNull(it.second) + + dataPost.observeOnce { + assertEquals(it, responseObject) } } @Test fun initiate_invalidBundleValues_successfulResponse() = runBlocking { //GIVEN - val currencyOne = "corrupted data" - val currencyTwo = "corrupted data again" val bundle = mock(Bundle()::class.java) val error = "Corrupted data found" //WHEN Mockito.`when`(bundle.getString("parse_1")).thenReturn(currencyOne) Mockito.`when`(bundle.getString("parse_2")).thenReturn(currencyTwo) - Mockito.`when`(helper.getDataFromApi(currencyOne, currencyTwo)) + Mockito.`when`(repository.getDataFromApi(currencyOne, currencyTwo)) .doAnswer { throw IOException(error) } //THEN viewModel.initiate(bundle) - viewModel.operationStartedListener.observeOnce { - assertEquals(true, it) - } - viewModel.operationFinishedListener.observeOnce { - assertEquals(false, it.first) - assertEquals(it.second, error) + + errorPost.observeOnce { + assertEquals(error, it) } } @Test fun initiate_sameBundleValues_successfulResponse() = runBlocking { //GIVEN - val currencyOne = "AUD - Australian Dollar" val bundle = mock(Bundle()::class.java) + val responseObject = mock(CurrencyModel::class.java) //WHEN Mockito.`when`(bundle.getString("parse_1")).thenReturn(null) Mockito.`when`(bundle.getString("parse_2")).thenReturn(null) Mockito.`when`(repository.getConversionPair()).thenReturn(Pair(currencyOne, currencyOne)) + Mockito.`when`(repository.getDataFromApi(currencyOne, currencyTwo)) + .thenReturn(responseObject) //THEN viewModel.initiate(bundle) - viewModel.operationStartedListener.observeOnce { - assertEquals(true, it) - } - viewModel.operationFinishedListener.observeOnce { - assertEquals(true, it.first) - assertNull(it.second) + + dataPost.observeOnce { + assertEquals(responseObject, it) } } @@ -129,12 +120,9 @@ class MainViewModelTest { //THEN viewModel.initiate(bundle) - viewModel.operationStartedListener.observeOnce { - assertEquals(true, it) - } - viewModel.operationFinishedListener.observeOnce { - assertEquals(false, it.first) - assertEquals("Select currencies", it.second) + + errorPost.observeOnce { + assertEquals("Select both currencies", it) } } @@ -142,11 +130,9 @@ class MainViewModelTest { @Test fun setCurrencyName_validValues_successResponse() = runBlocking { //GIVEN - val currencyOne = "AUD - Australian Dollar" - val currencyTwo = "GBP - British Pound" viewModel.rateIdTo = currencyTwo val tag = "top" - val responseObject = mock(ResponseObject::class.java) + val responseObject = mock(CurrencyModel::class.java) //WHEN Mockito.`when`(repository.getDataFromApi(currencyOne, currencyTwo)) @@ -154,24 +140,18 @@ class MainViewModelTest { //THEN viewModel.setCurrencyName(tag, currencyOne) - viewModel.operationStartedListener.observeOnce { - assertEquals(true, it) - } - viewModel.operationFinishedListener.observeOnce { - assertEquals(true, it.first) - Log.i("tag", "${it.first} ${it.second}") - assertNull(it.second) + + dataPost.observeOnce { + assertEquals(responseObject, it) } } @Test fun setCurrencyName_sameValues_successfulResponse() = runBlocking { //GIVEN - val currencyOne = "AUD - Australian Dollar" - val currencyTwo = "GBP - British Pound" viewModel.rateIdTo = currencyOne val tag = "top" - val responseObject = mock(ResponseObject::class.java) + val responseObject = mock(CurrencyModel::class.java) //WHEN Mockito.`when`(repository.getDataFromApi(currencyOne, currencyTwo)) @@ -179,35 +159,27 @@ class MainViewModelTest { //THEN viewModel.setCurrencyName(tag, currencyOne) - viewModel.operationStartedListener.observeOnce { - assertEquals(true, it) - } - viewModel.operationFinishedListener.observeOnce { - assertEquals(true, it.first) - assertNull(it.second) + dataPost.observeOnce { + assertEquals(responseObject, it) } } @Test fun setCurrencyName_invalidValues_unsuccessfulResponse() = runBlocking { //GIVEN - val currencyOne = "AUD - Australian Dollar" - val currencyTwo = "GBP - British Pound" + val error = "Data is corrupted" + viewModel.rateIdTo = "corrupted" val tag = "top" - val responseObject = mock(ResponseObject::class.java) + val responseObject = mock(CurrencyModel::class.java) //WHEN - Mockito.`when`(repository.getDataFromApi(currencyOne, currencyTwo)) - .thenReturn(responseObject) + Mockito.`when`(repository.getDataFromApi(currencyOne, "corrupted")) + .doAnswer { throw IOException(error) } //THEN viewModel.setCurrencyName(tag, currencyOne) - viewModel.operationStartedListener.observeOnce { - assertEquals(true, it) - } - viewModel.operationFinishedListener.observeOnce { - assertEquals(false, it.first) - assertNotNull(it.second) + errorPost.observeOnce { + assertEquals(error, it) } } diff --git a/app/src/test/java/com/appttude/h_mal/easycc/ui/widget/WidgetViewModelTest.kt b/app/src/test/java/com/appttude/h_mal/easycc/ui/widget/WidgetViewModelTest.kt index 9c8dfec..2fa6cbc 100644 --- a/app/src/test/java/com/appttude/h_mal/easycc/ui/widget/WidgetViewModelTest.kt +++ b/app/src/test/java/com/appttude/h_mal/easycc/ui/widget/WidgetViewModelTest.kt @@ -2,6 +2,7 @@ package com.appttude.h_mal.easycc.ui.widget import androidx.arch.core.executor.testing.InstantTaskExecutorRule import com.appttude.h_mal.easycc.data.repository.Repository +import com.appttude.h_mal.easycc.ui.BaseViewModelTest import com.appttude.h_mal.easycc.utils.observeOnce import org.junit.Assert.* import org.junit.Before @@ -15,21 +16,23 @@ import org.mockito.MockitoAnnotations private const val currencyOne = "AUD - Australian Dollar" private const val currencyTwo = "GBP - British Pound" -class WidgetViewModelTest { +class WidgetViewModelTest : BaseViewModelTest(){ // Run tasks synchronously @get:Rule val instantTaskExecutorRule: InstantTaskExecutorRule = InstantTaskExecutorRule() - lateinit var viewModel: WidgetViewModel + override lateinit var viewModel: WidgetViewModel @Mock lateinit var repository: Repository @Before - fun setUp() { + override fun setUp() { MockitoAnnotations.initMocks(this) viewModel = WidgetViewModel(repository) + + super.setUp() } @Test @@ -75,7 +78,6 @@ class WidgetViewModelTest { //THEN val dialogResult = viewModel.getSubmitDialogMessage() assertEquals(dialogResult, "Create widget for AUDGBP?") - } @Test @@ -86,9 +88,9 @@ class WidgetViewModelTest { //THEN viewModel.submitSelectionOnClick() - viewModel.operationFinishedListener.observeOnce { - assertEquals(it.first, true) - assertNull(it.second) + + dataPost.observeOnce { + assert(it is Unit) } } @@ -100,20 +102,23 @@ class WidgetViewModelTest { //THEN viewModel.submitSelectionOnClick() - viewModel.operationFinishedListener.observeOnce { - assertEquals(it.first, false) - assertNotNull(it.second) + + errorPost.observeOnce { + assertEquals("Selected rates cannot be the same", it) } } @Test fun submitSelectionOnClick_noInput_unsuccessfulResponse() { + //GIVEN + viewModel.rateIdFrom = null + viewModel.rateIdTo = null //THEN viewModel.submitSelectionOnClick() - viewModel.operationFinishedListener.observeOnce { - assertEquals(it.first, false) - assertNotNull(it.second) + + errorPost.observeOnce { + assertEquals("Selections incomplete", it) } } } \ No newline at end of file