Issue resolved with unit tests

Took 1 hour 31 minutes
This commit is contained in:
2022-08-28 00:56:07 +01:00
parent fd4cd93f78
commit b6283340f2
5 changed files with 91 additions and 88 deletions

View File

@@ -45,7 +45,7 @@ class WidgetViewModel @Inject constructor(
return return
} }
if (rateIdFrom == rateIdTo) { if (rateIdFrom == rateIdTo) {
onError("Selected rates cannot be the same ${rateIdFrom}${rateIdTo}") onError("Selected rates cannot be the same")
return return
} }
onSuccess(Unit) onSuccess(Unit)

View File

@@ -1,11 +1,13 @@
package com.appttude.h_mal.easycc.repository 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.BackupCurrencyApi
import com.appttude.h_mal.easycc.data.network.api.CurrencyApi 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.network.response.ResponseObject
import com.appttude.h_mal.easycc.data.prefs.PreferenceProvider 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.Repository
import com.appttude.h_mal.easycc.data.repository.RepositoryImpl 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 com.appttude.h_mal.easycc.utils.convertPairsListToString
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import okhttp3.ResponseBody import okhttp3.ResponseBody
@@ -22,7 +24,7 @@ import java.io.IOException
import kotlin.test.assertFailsWith import kotlin.test.assertFailsWith
class RepositoryNetworkTest { class RepositoryNetworkTest : SafeApiRequest() {
lateinit var repository: Repository lateinit var repository: Repository
@@ -49,15 +51,16 @@ class RepositoryNetworkTest {
//create a successful retrofit response //create a successful retrofit response
val mockCurrencyResponse = mock(ResponseObject::class.java) val mockCurrencyResponse = mock(ResponseObject::class.java)
val re = Response.success(mockCurrencyResponse) val re = Response.success(mockCurrencyResponse)
val currencyModel = mock(CurrencyModel::class.java)
//WHEN - loginApiRequest to return a successful response //WHEN - loginApiRequest to return a successful response
val currencyPair = convertPairsListToString(s1, s2) val currencyPair = convertPairsListToString(s1, s2)
Mockito.`when`(api.getCurrencyRate(currencyPair)).thenReturn(re) 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 //THEN - the unwrapped login response contains the correct values
val currencyResponse = repository.getDataFromApi(s1, s2) val currencyResponse = repository.getDataFromApi(s1, s2)
assertNotNull(currencyResponse) assertEquals(currencyResponse, currencyModel)
assertEquals(currencyResponse, mockCurrencyResponse)
} }
@Test @Test
@@ -74,6 +77,7 @@ class RepositoryNetworkTest {
//WHEN //WHEN
val currencyPair = convertPairsListToString(s1, s2) val currencyPair = convertPairsListToString(s1, s2)
Mockito.`when`(api.getCurrencyRate(currencyPair)).thenAnswer { re } Mockito.`when`(api.getCurrencyRate(currencyPair)).thenAnswer { re }
Mockito.`when`(apiBackup.getCurrencyRate(s1, s2)).thenAnswer { re }
//THEN - assert exception is not null //THEN - assert exception is not null
val ioExceptionReturned = assertFailsWith<IOException> { val ioExceptionReturned = assertFailsWith<IOException> {

View File

@@ -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<V : BaseViewModel> {
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<Any> = MutableLiveData()
var errorPost: MutableLiveData<String> = MutableLiveData()
}

View File

@@ -1,17 +1,17 @@
package com.appttude.h_mal.easycc.ui.main package com.appttude.h_mal.easycc.ui.main
import android.os.Bundle import android.os.Bundle
import android.util.Log
import androidx.arch.core.executor.testing.InstantTaskExecutorRule 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.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.MainCoroutineRule
import com.appttude.h_mal.easycc.utils.observeOnce import com.appttude.h_mal.easycc.utils.observeOnce
import com.nhaarman.mockitokotlin2.doAnswer import com.nhaarman.mockitokotlin2.doAnswer
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.runBlocking 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.Before
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test
@@ -22,7 +22,7 @@ import org.mockito.MockitoAnnotations
import java.io.IOException import java.io.IOException
@OptIn(ExperimentalCoroutinesApi::class) @OptIn(ExperimentalCoroutinesApi::class)
class MainViewModelTest { class MainViewModelTest : BaseViewModelTest<MainViewModel>(){
// Run tasks synchronously // Run tasks synchronously
@get:Rule @get:Rule
@@ -31,27 +31,27 @@ class MainViewModelTest {
@get:Rule @get:Rule
var mainCoroutineRule = MainCoroutineRule() var mainCoroutineRule = MainCoroutineRule()
lateinit var viewModel: MainViewModel override lateinit var viewModel: MainViewModel
@Mock @Mock
lateinit var repository: Repository lateinit var repository: Repository
@Mock private val currencyOne = "AUD - Australian Dollar"
lateinit var helper: CurrencyDataHelper private val currencyTwo = "GBP - British Pound"
@Before @Before
fun setUp() { override fun setUp() {
MockitoAnnotations.initMocks(this) MockitoAnnotations.initMocks(this)
viewModel = MainViewModel(helper, repository) viewModel = MainViewModel(repository)
super.setUp()
} }
@Test @Test
fun initiate_validBundleValues_successResponse() = runBlocking { fun initiate_validBundleValues_successResponse() = runBlocking {
//GIVEN //GIVEN
val currencyOne = "AUD - Australian Dollar"
val currencyTwo = "GBP - British Pound"
val bundle = mock(Bundle()::class.java) val bundle = mock(Bundle()::class.java)
val responseObject = mock(ResponseObject::class.java) val responseObject = mock(CurrencyModel::class.java)
//WHEN //WHEN
Mockito.`when`(bundle.getString("parse_1")).thenReturn(currencyOne) Mockito.`when`(bundle.getString("parse_1")).thenReturn(currencyOne)
@@ -61,59 +61,50 @@ class MainViewModelTest {
//THEN //THEN
viewModel.initiate(bundle) viewModel.initiate(bundle)
viewModel.operationStartedListener.observeOnce {
assertEquals(true, it) dataPost.observeOnce {
} assertEquals(it, responseObject)
viewModel.operationFinishedListener.observeOnce {
assertEquals(true, it.first)
assertNull(it.second)
} }
} }
@Test @Test
fun initiate_invalidBundleValues_successfulResponse() = runBlocking { fun initiate_invalidBundleValues_successfulResponse() = runBlocking {
//GIVEN //GIVEN
val currencyOne = "corrupted data"
val currencyTwo = "corrupted data again"
val bundle = mock(Bundle()::class.java) val bundle = mock(Bundle()::class.java)
val error = "Corrupted data found" val error = "Corrupted data found"
//WHEN //WHEN
Mockito.`when`(bundle.getString("parse_1")).thenReturn(currencyOne) Mockito.`when`(bundle.getString("parse_1")).thenReturn(currencyOne)
Mockito.`when`(bundle.getString("parse_2")).thenReturn(currencyTwo) Mockito.`when`(bundle.getString("parse_2")).thenReturn(currencyTwo)
Mockito.`when`(helper.getDataFromApi(currencyOne, currencyTwo)) Mockito.`when`(repository.getDataFromApi(currencyOne, currencyTwo))
.doAnswer { throw IOException(error) } .doAnswer { throw IOException(error) }
//THEN //THEN
viewModel.initiate(bundle) viewModel.initiate(bundle)
viewModel.operationStartedListener.observeOnce {
assertEquals(true, it) errorPost.observeOnce {
} assertEquals(error, it)
viewModel.operationFinishedListener.observeOnce {
assertEquals(false, it.first)
assertEquals(it.second, error)
} }
} }
@Test @Test
fun initiate_sameBundleValues_successfulResponse() = runBlocking { fun initiate_sameBundleValues_successfulResponse() = runBlocking {
//GIVEN //GIVEN
val currencyOne = "AUD - Australian Dollar"
val bundle = mock(Bundle()::class.java) val bundle = mock(Bundle()::class.java)
val responseObject = mock(CurrencyModel::class.java)
//WHEN //WHEN
Mockito.`when`(bundle.getString("parse_1")).thenReturn(null) Mockito.`when`(bundle.getString("parse_1")).thenReturn(null)
Mockito.`when`(bundle.getString("parse_2")).thenReturn(null) Mockito.`when`(bundle.getString("parse_2")).thenReturn(null)
Mockito.`when`(repository.getConversionPair()).thenReturn(Pair(currencyOne, currencyOne)) Mockito.`when`(repository.getConversionPair()).thenReturn(Pair(currencyOne, currencyOne))
Mockito.`when`(repository.getDataFromApi(currencyOne, currencyTwo))
.thenReturn(responseObject)
//THEN //THEN
viewModel.initiate(bundle) viewModel.initiate(bundle)
viewModel.operationStartedListener.observeOnce {
assertEquals(true, it) dataPost.observeOnce {
} assertEquals(responseObject, it)
viewModel.operationFinishedListener.observeOnce {
assertEquals(true, it.first)
assertNull(it.second)
} }
} }
@@ -129,12 +120,9 @@ class MainViewModelTest {
//THEN //THEN
viewModel.initiate(bundle) viewModel.initiate(bundle)
viewModel.operationStartedListener.observeOnce {
assertEquals(true, it) errorPost.observeOnce {
} assertEquals("Select both currencies", it)
viewModel.operationFinishedListener.observeOnce {
assertEquals(false, it.first)
assertEquals("Select currencies", it.second)
} }
} }
@@ -142,11 +130,9 @@ class MainViewModelTest {
@Test @Test
fun setCurrencyName_validValues_successResponse() = runBlocking { fun setCurrencyName_validValues_successResponse() = runBlocking {
//GIVEN //GIVEN
val currencyOne = "AUD - Australian Dollar"
val currencyTwo = "GBP - British Pound"
viewModel.rateIdTo = currencyTwo viewModel.rateIdTo = currencyTwo
val tag = "top" val tag = "top"
val responseObject = mock(ResponseObject::class.java) val responseObject = mock(CurrencyModel::class.java)
//WHEN //WHEN
Mockito.`when`(repository.getDataFromApi(currencyOne, currencyTwo)) Mockito.`when`(repository.getDataFromApi(currencyOne, currencyTwo))
@@ -154,24 +140,18 @@ class MainViewModelTest {
//THEN //THEN
viewModel.setCurrencyName(tag, currencyOne) viewModel.setCurrencyName(tag, currencyOne)
viewModel.operationStartedListener.observeOnce {
assertEquals(true, it) dataPost.observeOnce {
} assertEquals(responseObject, it)
viewModel.operationFinishedListener.observeOnce {
assertEquals(true, it.first)
Log.i("tag", "${it.first} ${it.second}")
assertNull(it.second)
} }
} }
@Test @Test
fun setCurrencyName_sameValues_successfulResponse() = runBlocking { fun setCurrencyName_sameValues_successfulResponse() = runBlocking {
//GIVEN //GIVEN
val currencyOne = "AUD - Australian Dollar"
val currencyTwo = "GBP - British Pound"
viewModel.rateIdTo = currencyOne viewModel.rateIdTo = currencyOne
val tag = "top" val tag = "top"
val responseObject = mock(ResponseObject::class.java) val responseObject = mock(CurrencyModel::class.java)
//WHEN //WHEN
Mockito.`when`(repository.getDataFromApi(currencyOne, currencyTwo)) Mockito.`when`(repository.getDataFromApi(currencyOne, currencyTwo))
@@ -179,35 +159,27 @@ class MainViewModelTest {
//THEN //THEN
viewModel.setCurrencyName(tag, currencyOne) viewModel.setCurrencyName(tag, currencyOne)
viewModel.operationStartedListener.observeOnce { dataPost.observeOnce {
assertEquals(true, it) assertEquals(responseObject, it)
}
viewModel.operationFinishedListener.observeOnce {
assertEquals(true, it.first)
assertNull(it.second)
} }
} }
@Test @Test
fun setCurrencyName_invalidValues_unsuccessfulResponse() = runBlocking { fun setCurrencyName_invalidValues_unsuccessfulResponse() = runBlocking {
//GIVEN //GIVEN
val currencyOne = "AUD - Australian Dollar" val error = "Data is corrupted"
val currencyTwo = "GBP - British Pound" viewModel.rateIdTo = "corrupted"
val tag = "top" val tag = "top"
val responseObject = mock(ResponseObject::class.java) val responseObject = mock(CurrencyModel::class.java)
//WHEN //WHEN
Mockito.`when`(repository.getDataFromApi(currencyOne, currencyTwo)) Mockito.`when`(repository.getDataFromApi(currencyOne, "corrupted"))
.thenReturn(responseObject) .doAnswer { throw IOException(error) }
//THEN //THEN
viewModel.setCurrencyName(tag, currencyOne) viewModel.setCurrencyName(tag, currencyOne)
viewModel.operationStartedListener.observeOnce { errorPost.observeOnce {
assertEquals(true, it) assertEquals(error, it)
}
viewModel.operationFinishedListener.observeOnce {
assertEquals(false, it.first)
assertNotNull(it.second)
} }
} }

View File

@@ -2,6 +2,7 @@ package com.appttude.h_mal.easycc.ui.widget
import androidx.arch.core.executor.testing.InstantTaskExecutorRule import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import com.appttude.h_mal.easycc.data.repository.Repository 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 com.appttude.h_mal.easycc.utils.observeOnce
import org.junit.Assert.* import org.junit.Assert.*
import org.junit.Before import org.junit.Before
@@ -15,21 +16,23 @@ import org.mockito.MockitoAnnotations
private const val currencyOne = "AUD - Australian Dollar" private const val currencyOne = "AUD - Australian Dollar"
private const val currencyTwo = "GBP - British Pound" private const val currencyTwo = "GBP - British Pound"
class WidgetViewModelTest { class WidgetViewModelTest : BaseViewModelTest<WidgetViewModel>(){
// Run tasks synchronously // Run tasks synchronously
@get:Rule @get:Rule
val instantTaskExecutorRule: InstantTaskExecutorRule = InstantTaskExecutorRule() val instantTaskExecutorRule: InstantTaskExecutorRule = InstantTaskExecutorRule()
lateinit var viewModel: WidgetViewModel override lateinit var viewModel: WidgetViewModel
@Mock @Mock
lateinit var repository: Repository lateinit var repository: Repository
@Before @Before
fun setUp() { override fun setUp() {
MockitoAnnotations.initMocks(this) MockitoAnnotations.initMocks(this)
viewModel = WidgetViewModel(repository) viewModel = WidgetViewModel(repository)
super.setUp()
} }
@Test @Test
@@ -75,7 +78,6 @@ class WidgetViewModelTest {
//THEN //THEN
val dialogResult = viewModel.getSubmitDialogMessage() val dialogResult = viewModel.getSubmitDialogMessage()
assertEquals(dialogResult, "Create widget for AUDGBP?") assertEquals(dialogResult, "Create widget for AUDGBP?")
} }
@Test @Test
@@ -86,9 +88,9 @@ class WidgetViewModelTest {
//THEN //THEN
viewModel.submitSelectionOnClick() viewModel.submitSelectionOnClick()
viewModel.operationFinishedListener.observeOnce {
assertEquals(it.first, true) dataPost.observeOnce {
assertNull(it.second) assert(it is Unit)
} }
} }
@@ -100,20 +102,23 @@ class WidgetViewModelTest {
//THEN //THEN
viewModel.submitSelectionOnClick() viewModel.submitSelectionOnClick()
viewModel.operationFinishedListener.observeOnce {
assertEquals(it.first, false) errorPost.observeOnce {
assertNotNull(it.second) assertEquals("Selected rates cannot be the same", it)
} }
} }
@Test @Test
fun submitSelectionOnClick_noInput_unsuccessfulResponse() { fun submitSelectionOnClick_noInput_unsuccessfulResponse() {
//GIVEN
viewModel.rateIdFrom = null
viewModel.rateIdTo = null
//THEN //THEN
viewModel.submitSelectionOnClick() viewModel.submitSelectionOnClick()
viewModel.operationFinishedListener.observeOnce {
assertEquals(it.first, false) errorPost.observeOnce {
assertNotNull(it.second) assertEquals("Selections incomplete", it)
} }
} }
} }