我有这个视图模型:
class MyViewModel(private val myUseCase: MyUseCase) : ViewModel() {
val stateLiveData = MutableLiveData(State.IDLE)
fun onButtonPressed() {
viewModelScope.launch {
stateLiveData.value = State.LOADING
myUseCase.loadStuff() // Suspend
stateLiveData.value = State.SUCCESS
}
}
}
我想编写一个测试,以检查LOADING
运行时状态是否真的为myUseCase.loadStuff()
。我正在使用MockK。这是测试课程:
@ExperimentalCoroutinesApi
class MyViewModelTest {
@get:Rule
val rule = InstantTaskExecutorRule()
private lateinit var myUseCase: MyUseCase
private lateinit var myViewModel: MyViewModel
@Before
fun setup() {
myUseCase = mockkClass(MyUseCase::class)
myViewModel = MyViewModel(myUseCase)
}
@Test
fun `button click should put screen into loading state`() = runBlockingTest {
coEvery { myUseCase.loadStuff() } coAnswers { delay(2000) }
myViewModel.onButtonPressed()
advanceTimeBy(1000)
val state = myViewModel.stateLiveData.value
assertEquals(State.LOADING, state)
}
}
失败:
java.lang.AssertionError:
Expected :LOADING
Actual :IDLE
我该如何解决?
答案 0 :(得分:1)
您的问题在于viewModelScope
分发给Dispatcher.MAIN
,而不是runBlockingTest
创建的测试分发者。这意味着即使调用advanceTimeBy
,代码也不会执行。
您可以通过使用Dispatcher.setMain(..)
用测试调度程序替换MAIN调度程序来解决此问题。这将需要自己管理调度程序,而不是依靠独立的runBlockingTest
。
答案 1 :(得分:1)
我只需要在测试类中进行一些更改即可使其通过:
@ExperimentalCoroutinesApi
class MyViewModelTest {
@get:Rule
val rule = InstantTaskExecutorRule()
private val dispatcher = TestCoroutineDispatcher()
private lateinit var myUseCase: MyUseCase
private lateinit var myViewModel: MyViewModel
@Before
fun setup() {
Dispatchers.setMain(dispatcher)
myUseCase = mockkClass(MyUseCase::class)
myViewModel = MyViewModel(myUseCase)
}
@After
fun cleanup() {
Dispatchers.resetMain()
}
@Test
fun `button click should put screen into loading state`() {
dispatcher.runBlockingTest {
coEvery { myUseCase.loadStuff() } coAnswers { delay(2000) }
myViewModel.onButtonPressed()
// This isn't even needed.
advanceTimeBy(1000)
val state = myViewModel.stateLiveData.value
assertEquals(State.LOADING, state)
}
}
}
完全不需要在视图模型中进行任何更改! :D
感谢Kiskae提供有用的建议!