'Mocked function returns null after invoking in tested function
I am begginer in testing android apps. I have problem while testing my viewmodel. I want to test view model function which gets data from repository and make some logic with it.
class CurrencyViewModel(
private val repository: Repository
) : ViewModel() {
private var day: LocalDate = LocalDate.now()
private val listWithFormattedCurrencies = mutableListOf<CurrencyInfo>()
var currencyList = MutableLiveData<MutableList<CurrencyInfo>>()
fun getRecordsFromFixer() {
CoroutineScope(Dispatchers.IO).launch {
var listWithCurrencies: MutableList<CurrencyInfo>
val formatter = DateTimeFormatter.ISO_LOCAL_DATE
val formatted = day.format(formatter)
listWithCurrencies = repository.getRecordsFromApi(formatted)
for (i in listWithCurrencies) {
val formattedRate = formattingRates(i.currency, i.rate)
listWithFormattedCurrencies.add(CurrencyInfo(i.date, i.currency, formattedRate))
}
currencyList.postValue(listWithFormattedCurrencies)
day = day.minusDays(1)
}
}
private fun formattingRates(name : String, rate : String): String {
return if(name =="BTC" && name.isNotEmpty() && rate.isNotEmpty()){
String.format("%.8f", rate.toDouble())
}else if (name.isNotEmpty() && rate.isNotEmpty()){
String.format("%.4f", rate.toDouble())
}else{
rate
}
}
}
class CurrencyViewModelTest {
@ExperimentalCoroutinesApi
private val testDispatcher = StandardTestDispatcher()
@get:Rule
val rule = InstantTaskExecutorRule()
private lateinit var viewModel: CurrencyViewModel
@Before
fun setUp() {
Dispatchers.setMain(testDispatcher)
}
@After
fun tearDown(){
Dispatchers.resetMain()
}
@Test
fun `formatting empty name returns empty rate` () = runBlocking{
val currencyRepository = mock(currencyRepository::class.java)
val viewModel = CurrencyViewModel(currencyRepository)
val date = "2022-02-02"
val listOfCurrencies = mutableListOf(CurrencyInfo(date,"", "0.222"))
Mockito.`when` (currencyRepository.getRecordsFromApi(date)).thenReturn(listOfCurrencies)
viewModel.getRecordsFromFixer()
assertThat(viewModel.currencyList.getOrAwaitValue().contains(CurrencyInfo(date,"",""))).isTrue()
}
}
Error
Exception in thread "DefaultDispatcher-worker-1 @coroutine#2" java.lang.NullPointerException
.
.
.
LiveData value was never set.
java.util.concurrent.TimeoutException: LiveData value was never set.
The problem is that currencyRepository.getRecordsFromApi(date)) returns null object.
I tried to check if currencyRepository.getRecordsFromApi(date)) returns proper data after initializing it apart from ViewModel function and it works fine.
Why currencyRepository.getRecordsFromApi(date)) returns null when it is invoked in ViewModel function?
Solution 1:[1]
I finally found answer. I used Mockk library and rebulid my ViewModel.
class CurrencyViewModel(
private val repository: Repository
) : ViewModel() {
private var day: LocalDate = LocalDate.now()
private var formattedDate = getDate(day)
var listWithFormattedCurrencies = mutableListOf<CurrencyInfo>()
var currencyList = MutableLiveData<MutableList<CurrencyInfo>>()
var listWithCurrencies = mutableListOf<CurrencyInfo>()
fun getRecordsFromFixer() {
CoroutineScope(Dispatchers.IO).launch {
listWithCurrencies = getRecords()
for (i in listWithCurrencies) {
val formattedRate = formattingRates(i.currency, i.rate)
listWithFormattedCurrencies.add(CurrencyInfo(i.date, i.currency, formattedRate))
}
currencyList.postValue(listWithFormattedCurrencies)
day = day.minusDays(1)
formattedDate = getDate(day)
}
}
private fun formattingRates(name : String, rate : String): String {
return if(name =="BTC" && name.isNotEmpty() && rate.isNotEmpty()){
String.format("%.8f", rate.toDouble())
}else if (name.isNotEmpty() && rate.isNotEmpty()){
String.format("%.4f", rate.toDouble())
}else{
rate
}
}
private suspend fun getRecords() : MutableList<CurrencyInfo>{
return repository.getRecordsFromApi(formattedDate)
}
private fun getDate(day : LocalDate) : String{
val formatter = DateTimeFormatter.ISO_LOCAL_DATE
return day.format(formatter)
}
}
class CurrencyViewModelTest {
private lateinit var repository: currencyRepository
private lateinit var viewModel: CurrencyViewModel
@ExperimentalCoroutinesApi
private val testDispatcher = StandardTestDispatcher()
@get:Rule
val rule = InstantTaskExecutorRule()
@Before
fun setUp() {
repository = currencyRepository()
viewModel = spyk(CurrencyViewModel(repository), recordPrivateCalls = true)
Dispatchers.setMain(testDispatcher)
}
@After
fun tearDown(){
Dispatchers.resetMain()
}
@Test
fun `formatting empty name and rate returns empty name and rate` () = runBlocking{
val date = "2022-02-02"
val listOfCurrencies = mutableListOf(CurrencyInfo(date,"", ""))
coEvery{ viewModel["getRecords"]()} returns listOfCurrencies
coEvery { viewModel.getRecordsFromFixer() } answers {callOriginal()}
viewModel.getRecordsFromFixer()
assertEquals(mutableListOf(CurrencyInfo(date,"", "")), viewModel.currencyList.getOrAwaitValue())
}
@Test
fun `receiving proper formatted data` () = runBlocking{
val date = "2022-02-02"
val listOfCurrencies = mutableListOf(CurrencyInfo(date,"USD", "0.4567234124"))
coEvery{ viewModel["getRecords"]()} returns listOfCurrencies
coEvery { viewModel.getRecordsFromFixer() } answers {callOriginal()}
viewModel.getRecordsFromFixer()
assertEquals(mutableListOf(CurrencyInfo(date,"USD", "0,4567")), viewModel.currencyList.getOrAwaitValue())
}
@Test
fun `receiving proper formatted BTC rate` () = runBlocking{
val date = "2022-02-02"
val listOfCurrencies = mutableListOf(CurrencyInfo(date,"BTC", "0.45672341241412345435654367645"))
coEvery{ viewModel["getRecords"]()} returns listOfCurrencies
coEvery { viewModel.getRecordsFromFixer() } answers {callOriginal()}
viewModel.getRecordsFromFixer()
assertEquals(mutableListOf(CurrencyInfo(date,"BTC", "0,45672341")), viewModel.currencyList.getOrAwaitValue())
}
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | Filipm1996 |
