简体   繁体   English

使用 MockK 测试 LiveData 和协程

[英]Test LiveData and Coroutines using MockK

I have this view model:我有这个视图模型:

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
        }
    }
}

I'd like to write a test that checks whether the state is really LOADING while myUseCase.loadStuff() is running.我想编写一个测试,检查在myUseCase.loadStuff()运行时状态是否真的为LOADING I'm using MockK for that.我正在使用 MockK。 Here's the test 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)
    }
}

It fails:它失败:

java.lang.AssertionError: 
Expected :LOADING
Actual   :IDLE

How can I fix this?我怎样才能解决这个问题?

I only needed to make a few changes in the test class to make it pass:我只需要在测试类中进行一些更改即可通过:

@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)
        }
    }
}

No changes needed in the view model at all!视图模型根本不需要更改! :D :D

Thanks Kiskae for such helpful advice!感谢 Kiskae 提供如此有用的建议!

Your problem lies in the fact that viewModelScope dispatches to Dispatcher.MAIN , not the testing dispatcher created by runBlockingTest .您的问题在于viewModelScope分派到Dispatcher.MAIN ,而不是由runBlockingTest创建的测试分派runBlockingTest This means that even with the call to advanceTimeBy the code does not get executed.这意味着即使调用了advanceTimeBy代码也不会被执行。

You can solve the issue by using Dispatcher.setMain(..) to replace the MAIN dispatcher with your test dispatcher.您可以通过使用Dispatcher.setMain(..)将 MAIN 调度程序替换为您的测试调度程序来解决该问题。 This will require managing the dispatcher yourself instead of relying on the stand-alone runBlockingTest .这将需要您自己管理调度程序,而不是依赖于独立的runBlockingTest

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

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