4

I have a repository class that uses a MutableLiveData object, exposed as just LiveData, to return results of async web queries to a ViewModel. The ViewModel then uses Transformation to map the results to another MutableLiveData which is observed by a View.

I think I followed the recommended architecture in this module by separating the concerns, but I find it hard to write unit tests for the ViewModel:

class DataRepository ( private val webservice: DataWebService ) {

    private val _exception = MutableLiveData<Exception?>(null)
    val exception : LiveData<Exception?> get() = _exception

    private val _data = MutableLiveData<List<DataItem>>()

    val data: LiveData<List<DataItem>> = _data

    private val responseListener = Response.Listener<String> {response ->
        try {
            val list = JsonReader(SearchResult.mapping).readObject(response).map {
                    //Data transformation
            }
            _exception.value = null
            _data.value = list
        } catch (ex: Exception) {
            _exception.value = ex
            _data.value = emptyList()
        } 
    }

    fun findData(searchString: String) {
        _data.value = emptyList()
        webservice.findData(searchString, responseListener = responseListener)
    } 
}
class WebServiceDataViewModel (private val repository: DataRepository, app: App) : AndroidViewModel(app)
{

    val dataList: LiveData<List<DataItem>> = Transformations.map(repository.data) {
        _showEmpty.value = it.isEmpty()
        it
    }
    val exception: LiveData<Exception?> get() = repository.exception

    private val _showEmpty = MutableLiveData(true)
    val showEmpty : LiveData<Boolean> = _showEmpty

    private var _reloadOnCreate = true

    var searchString: String? = null
        set(value) {
            field = value
            if (!value.isNullOrBlank()) {
                repository.findData(value)
            }
        }
}

ViewModel Test class:

@RunWith(JUnit4::class)
class WebServicePodcastViewModelTest {
    @Rule var instantExecutorRule = InstantTaskExecutorRule()

    @Mock lateinit var repository : DataRepository
    @Mock lateinit var app : App

    lateinit var viewModel: WebServiceDataViewModel

    @Mock lateinit var exceptionObserver : Observer<Exception?>
    @Mock lateinit var dataObserver : Observer<List<DataItem>>
    @Mock lateinit var showEmptyObserver : Observer<Boolean>

    @Before
    fun setUp() {
        MockitoAnnotations.initMocks(this)

        viewModel = WebServiceDataViewModel(repository, app)
        viewModel.exception.observeForever(exceptionObserver)
        viewModel.showEmpty.observeForever(showEmptyObserver)
        viewModel.dataList.observeForever(dataObserver)
    }

    @Test
    fun searchForData() {
        //given
        val searchString = "MockSearch"
        //when
        `when`(repository.findData(searchString)).then { /* How to invoke the mock repositories LiveData? */ }
        //then
        //TODO: verify that ViewModel LiveData behaves as expected
    }
}

So how do I invoke the immutable LiveData of a mocked class? Currently I'm trying to use Mockito & JUnit, but I'm open to different frameworks if the setup is easy!

ThePMO
  • 413
  • 4
  • 13

2 Answers2

1

In the end I ditched Mockito and used MockK instead which works like a charm!

ThePMO
  • 413
  • 4
  • 13
1

You must take into consideration that you need to set and observe in the test the livedata which is observed from the view, then you can do something like this: (As good practice, instead of having different Livedatas depending on the type od result, you can use a Sealed Class with generics to encapsulate your UI's state and to facilitate the process of observing different livedata rather than just one)

@RunWith(MockitoJUnitRunner::class)
class WebServicePodcastViewModelTest {
  @Rule var instantExecutorRule = InstantTaskExecutorRule()

  @Mock lateinit var repository : DataRepository
  @Mock lateinit var app : App

  lateinit var viewModel: WebServiceDataViewModel

  @Mock lateinit var exceptionObserver : Observer<Exception?>
  @Mock lateinit var dataObserver : Observer<List<DataItem>>
  @Mock lateinit var showEmptyObserver : Observer<Boolean>

  @Before
  fun setUp() {
    viewModel = WebServiceDataViewModel(repository, app)
    viewModel.exception.observeForever(exceptionObserver)
    viewModel.showEmpty.observeForever(showEmptyObserver)
    viewModel.dataList.observeForever(dataObserver)
  }

  @Test
  fun searchForData() {
    //given
    val searchString = "MockSearch"
    val dataResponse = MutableLiveData<List<DataItem>>()
    dataResponse.value = listOf<DataItem>()
    //when
    `when`(repository.findData(searchString)).then { 
         dataResponse
    }
    //then
    assertNotNull(viewModel.getDataList().value)
    assertEquals(viewModel.getDataList().value,  emptyList()) /* empty list must be an object of the same type of listOf<DataItem>() */
 }
}
Carlos Daniel
  • 2,459
  • 25
  • 30