使用 MockK 测试 LiveData 和协程
Test LiveData and Coroutines using MockK
我有这个视图模型:
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。这是测试 class:
@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
我该如何解决这个问题?
您的问题在于 viewModelScope
调度到 Dispatcher.MAIN
,而不是 runBlockingTest
创建的测试调度程序。这意味着即使调用 advanceTimeBy
代码也不会执行。
您可以通过使用 Dispatcher.setMain(..)
将 MAIN 调度程序替换为您的测试调度程序来解决此问题。这将需要您自己管理调度程序,而不是依赖独立的 runBlockingTest
.
我只需要在测试中做一些更改 class 就可以通过:
@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 提供如此有用的建议!
我有这个视图模型:
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。这是测试 class:
@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
我该如何解决这个问题?
您的问题在于 viewModelScope
调度到 Dispatcher.MAIN
,而不是 runBlockingTest
创建的测试调度程序。这意味着即使调用 advanceTimeBy
代码也不会执行。
您可以通过使用 Dispatcher.setMain(..)
将 MAIN 调度程序替换为您的测试调度程序来解决此问题。这将需要您自己管理调度程序,而不是依赖独立的 runBlockingTest
.
我只需要在测试中做一些更改 class 就可以通过:
@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 提供如此有用的建议!