简体   繁体   English

Android 单元测试:如何模拟包含 MutableLiveData 但仅公开 LiveData 的 object?

[英]Android Unit Test: How to mock an object which contains MutableLiveData but only exposes LiveData?

I have a repository class that uses a MutableLiveData object, exposed as just LiveData, to return results of async web queries to a ViewModel.我有一个存储库 class,它使用 MutableLiveData object,仅作为 LiveData 公开,将异步 web 查询的结果返回给 ViewModel。 The ViewModel then uses Transformation to map the results to another MutableLiveData which is observed by a View.然后 ViewModel 使用 map 的转换,将结果转换为另一个 MutableLiveData,由 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:我认为我通过分离关注点遵循了本模块中推荐的架构,但我发现很难为 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: ViewModel 测试 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?那么如何调用模拟的 class 的不可变 LiveData? Currently I'm trying to use Mockito & JUnit, but I'm open to different frameworks if the setup is easy!目前我正在尝试使用 Mockito 和 JUnit,但如果设置简单,我对不同的框架持开放态度!

In the end I ditched Mockito and used MockK instead which works like a charm!最后,我放弃了 Mockito 并改用MockK ,这就像一个魅力!

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)您必须考虑到您需要在测试中设置和观察从视图中观察到的 livedata,然后您可以执行以下操作:(作为一种好的做法,您可以根据类型 od 结果使用不同的 Livedata,而不是可以使用密封的 Class 和 generics 来封装你的 UI 的 state 并促进观察不同实时数据的过程,而不仅仅是一个)

@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>() */
 }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM