'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