接收流的Android单元测试视图模型
我有一个ViewModel,它与用例对话并得到一个流,即接收流的Android单元测试视图模型,android,kotlin,junit4,kotlin-coroutines,android-mvvm,Android,Kotlin,Junit4,Kotlin Coroutines,Android Mvvm,我有一个ViewModel,它与用例对话并得到一个流,即flow。我想对我的ViewModel进行单元测试。我不熟悉使用流。需要帮助请。以下是viewModel- class MyViewModel(private val handle: SavedStateHandle, private val useCase: MyUseCase) : ViewModel() { private val viewState = MyViewState() fun onOp
flow
。我想对我的ViewModel进行单元测试。我不熟悉使用流。需要帮助请。以下是viewModel-
class MyViewModel(private val handle: SavedStateHandle, private val useCase: MyUseCase) : ViewModel() {
private val viewState = MyViewState()
fun onOptionsSelected() =
useCase.getListOfChocolates(MyAction.GetChocolateList).map {
when (it) {
is MyResult.Loading -> viewState.copy(loading = true)
is MyResult.ChocolateList -> viewState.copy(loading = false, data = it.choclateList)
is MyResult.Error -> viewState.copy(loading = false, error = "Error")
}
}.asLiveData(Dispatchers.Default + viewModelScope.coroutineContext)
MyViewState看起来像这样-
data class MyViewState(
val loading: Boolean = false,
val data: List<ChocolateModel> = emptyList(),
val error: String? = null
)
数据类MyViewState(
val加载:布尔值=false,
val数据:List=emptyList(),
val错误:字符串?=null
)
单元测试如下所示。我总是不知道我做错了什么
class MyViewModelTest {
@get:Rule
val instantExecutorRule = InstantTaskExecutorRule()
private val mainThreadSurrogate = newSingleThreadContext("UI thread")
private lateinit var myViewModel: MyViewModel
@Mock
private lateinit var useCase: MyUseCase
@Mock
private lateinit var handle: SavedStateHandle
@Mock
private lateinit var chocolateList: List<ChocolateModel>
private lateinit var viewState: MyViewState
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
Dispatchers.setMain(mainThreadSurrogate)
viewState = MyViewState()
myViewModel = MyViewModel(handle, useCase)
}
@After
fun tearDown() {
Dispatchers.resetMain() // reset main dispatcher to the original Main dispatcher
mainThreadSurrogate.close()
}
@Test
fun onOptionsSelected() {
runBlocking {
val flow = flow {
emit(MyResult.Loading)
emit(MyResult.ChocolateList(chocolateList))
}
Mockito.`when`(useCase.getListOfChocolates(MyAction.GetChocolateList)).thenReturn(flow)
myViewModel.onOptionsSelected().observeForever {}
viewState.copy(loading = true)
assertEquals(viewState.loading, true)
viewState.copy(loading = false, data = chocolateList)
assertEquals(viewState.data.isEmpty(), false)
assertEquals(viewState.loading, true)
}
}
}
类MyViewModelTest{
@获取:规则
val instantExecutorRule=instantetaskexecutorrule()
private val mainthreadsprogate=newSingleThreadContext(“UI线程”)
私有lateinit var myViewModel:myViewModel
@嘲弄
私有lateinit变量用例:MyUseCase
@嘲弄
私有lateinit变量句柄:SavedStateHandle
@嘲弄
私有lateinit变量巧克力列表:列表
私有lateinit变量viewState:MyViewState
@以前
趣味设置(){
initMocks(this)
Dispatchers.setMain(mainthreadsubrogate)
viewState=MyViewState()
myViewModel=myViewModel(句柄,用例)
}
@之后
有趣的撕裂{
Dispatchers.resetMain()//将主调度器重置为原始主调度器
mainthreadsrogate.close()
}
@试验
趣味onoptionselected(){
运行阻塞{
val流量=流量{
发射(MyResult.Loading)
发出(MyResult.ChocolateList(ChocolateList))
}
Mockito.`when`(useCase.getListOfChocolates(MyAction.getChocolates))。然后返回(flow)
myViewModel.OnOptionSelected().ObserveForRever{}
viewState.copy(加载=true)
assertEquals(viewState.loading,true)
copy(加载=假,数据=巧克力列表)
assertEquals(viewState.data.isEmpty(),false)
assertEquals(viewState.loading,true)
}
}
}
此测试环境中存在以下几个问题:
流
生成器将立即发出结果,因此始终会收到最后一个值viewState
持有者与我们的mock没有链接,因此是无用的delay
在流生成器中处理这两个值viewState
MainCoroutineScopeRule
控制延迟执行流ArgumentCaptor
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import androidx.lifecycle.Observer
import androidx.lifecycle.SavedStateHandle
import com.pavneet_singh.temp.ui.main.testflow.*
import org.junit.Assert.assertEquals
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.runBlocking
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.mockito.ArgumentCaptor
import org.mockito.Captor
import org.mockito.Mock
import org.mockito.Mockito.*
import org.mockito.MockitoAnnotations
class MyViewModelTest {
@get:Rule
val instantExecutorRule = InstantTaskExecutorRule()
@get:Rule
val coroutineScope = MainCoroutineScopeRule()
@Mock
private lateinit var mockObserver: Observer<MyViewState>
private lateinit var myViewModel: MyViewModel
@Mock
private lateinit var useCase: MyUseCase
@Mock
private lateinit var handle: SavedStateHandle
@Mock
private lateinit var chocolateList: List<ChocolateModel>
private lateinit var viewState: MyViewState
@Captor
private lateinit var captor: ArgumentCaptor<MyViewState>
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
viewState = MyViewState()
myViewModel = MyViewModel(handle, useCase)
}
@Test
fun onOptionsSelected() {
runBlocking {
val flow = flow {
emit(MyResult.Loading)
delay(10)
emit(MyResult.ChocolateList(chocolateList))
}
`when`(useCase.getListOfChocolates(MyAction.GetChocolateList)).thenReturn(flow)
`when`(chocolateList.get(0)).thenReturn(ChocolateModel("Pavneet", 1))
val liveData = myViewModel.onOptionsSelected()
liveData.observeForever(mockObserver)
verify(mockObserver).onChanged(captor.capture())
assertEquals(true, captor.value.loading)
coroutineScope.advanceTimeBy(10)
verify(mockObserver, times(2)).onChanged(captor.capture())
assertEquals("Pavneet", captor.value.data[0].name)// name is custom implementaiton field of `ChocolateModel` class
}
}
}
查看Github上的repo以完成实现 我想我已经找到了一个更好的方法来测试这一点,通过使用Channel和
consumerasflow
扩展函数。至少在我的测试中,我似乎能够测试通过通道发送的多个值(作为流使用)
所以。。假设您有一些用例组件公开了一个流
。在ViewModelTest
中,您希望检查每次发出值时,UI状态是否会更新为某个值。
在我的例子中,UI状态是一个StateFlow
,但这也应该可以通过LiveData实现。
此外,我正在使用MockK,但使用Mockito也应该很容易
鉴于此,以下是我的测试结果:
@Test
fun test() = runBlocking(testDispatcher) {
val channel = Channel<String>()
every { mockedUseCase.someDataFlow } returns channel.consumeAsFlow()
channel.send("a")
assertThat(viewModelUnderTest.uiState.value, `is`("a"))
channel.send("b")
assertThat(viewModelUnderTest.uiState.value, `is`("b"))
}
@测试
fun test()=运行阻塞(testDispatcher){
val通道=通道()
每个{mockedUseCase.someDataFlow}返回channel.consumerasflow()
频道发送(“a”)
断言(viewModelUnderTest.uiState.value,`is`(“a”))
频道发送(“b”)
断言(viewModelUnderTest.uiState.value,`is`(“b”))
}
编辑:我想您也可以使用任何类型的热流实现,而不是
Channel
和consumerasflow
。例如,您可以使用MutableSharedFlow
,它允许您在需要时发出值。您需要在此处测试什么?需要测试的确切场景是什么?从用文字描述测试开始。@Code peedient-我需要测试viewmodel是否从用例接收到流,并在其上构建视图状态并将其提供给observer@Maria哪种方法有效?onOptions Selected()在viewmodel@IgorGanapolsky我的片段观察到一个livedata,因此我需要在ViewModel中为流做一个livedata我们如何以这种方式测试livedata?@IgorGanapolsky有多种方法,如果你有一个模拟或伪造的存储库,那么你可以用所需的数据触发事件。或者您可以使用livedatabuilder或@Pavneet_Singh如果收到的数据是PagingData
?@Dr.jacky根据您的实现和流程,您可能需要设置模拟对象或面。“src/test/”和“src/androidTest”文件夹之间的区别是什么?它是如何在主线程中处理viewmodel函数的执行的?
@Test
fun test() = runBlocking(testDispatcher) {
val channel = Channel<String>()
every { mockedUseCase.someDataFlow } returns channel.consumeAsFlow()
channel.send("a")
assertThat(viewModelUnderTest.uiState.value, `is`("a"))
channel.send("b")
assertThat(viewModelUnderTest.uiState.value, `is`("b"))
}