Mocked function returns null after invoking in tested function

Viewed 50

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?

1 Answers

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())
    }
Related