Android 单元测试:如何模拟包含 MutableLiveData 但仅公开 LiveData 的对象?
Android Unit Test: How to mock an object which contains MutableLiveData but only exposes LiveData?
我有一个存储库 class,它使用 MutableLiveData 对象(仅作为 LiveData 公开)到 return 对 ViewModel 的异步 Web 查询结果。 ViewModel 然后使用 Transformation 将结果映射到另一个 MutableLiveData,该数据由 View 观察。
我想我通过分离关注点遵循了本模块中推荐的架构,但我发现很难为 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 测试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
}
}
那么如何调用模拟 class 的不可变 LiveData?目前我正在尝试使用 Mockito 和 JUnit,但如果设置简单,我愿意接受不同的框架!
最后我放弃了 Mockito,改用了 MockK,效果非常好!
你必须考虑到你需要在测试中设置和观察从视图观察到的实时数据,那么你可以这样做:
(作为一种好的做法,您可以使用带有泛型的 Sealed Class 来封装您的 UI 的状态并促进观察不同实时数据的过程,而不是根据类型 od 结果使用不同的 Livedata不仅仅是一个)
@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>() */
}
}
我有一个存储库 class,它使用 MutableLiveData 对象(仅作为 LiveData 公开)到 return 对 ViewModel 的异步 Web 查询结果。 ViewModel 然后使用 Transformation 将结果映射到另一个 MutableLiveData,该数据由 View 观察。
我想我通过分离关注点遵循了本模块中推荐的架构,但我发现很难为 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 测试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
}
}
那么如何调用模拟 class 的不可变 LiveData?目前我正在尝试使用 Mockito 和 JUnit,但如果设置简单,我愿意接受不同的框架!
最后我放弃了 Mockito,改用了 MockK,效果非常好!
你必须考虑到你需要在测试中设置和观察从视图观察到的实时数据,那么你可以这样做: (作为一种好的做法,您可以使用带有泛型的 Sealed Class 来封装您的 UI 的状态并促进观察不同实时数据的过程,而不是根据类型 od 结果使用不同的 Livedata不仅仅是一个)
@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>() */
}
}