1

如何使用 Robolectric 在测试范围内使用 LiveData 观察者启动片段

分段

class MyFragment(private val viewModel: MyViewModel) : Fragment() {

    ...

    fun myObserver {
        ... 
        // If I remove this observer the test will pass. 
        viewModel.MyLiveData.observe(viewLifecycleOwner, Observer{
            ...
        }
    }
}

我的测试使用 RobolectricTestRunner,因此我可以在测试范围内启动片段。

@RunWith(robolectricTestRunner::class) 
class MyFragmentTest {

    // Executes tasks in the Architecture Components in the same thread
    @get:Rule
    var instantTaskExecutorRule = InstantTaskExecutorRule()

    @Test
    fun testOne() {
        val viewModel: MyViewModel = mock(MyViewModel::class.java)
        val scenario = launchFragmentInContainer(
            factory = MainFragmentFactory(viewModel),
            fragmentArgs = null
            themeResId = R.style.Theme_MyTheme
        )
        // Tried implementing shadowOf as the error suggests. 

    } 
}

尝试运行测试时出现以下错误。我尝试在实例化 FragmentScenario 之前和之后将 Main looper 设置为空闲。

java.lang.Exception: Main looper has queued unexecuted runnables. This might be the cause of the test failure. You might need a shadowOf(getMainLooper()).idle() call.

我试过以下

  • 为 Main Looper 实现一个影子类。使用 Looper 模式注释类。
@RunWith(RobolectricTestRunner::class)
@LooperMode(LooperMode.Mode.PAUSED)
class MyFragmentTest {
  • 添加场景状态
    scenario.moveToState(Lifecycle.State.CREATED)
    scenario.moveToState(Lifecycle.State.RESUMED)

我的测试依赖项。

    // Test
    testImplementation 'androidx.arch.core:core-testing:2.1.0'
    testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.4.3'
    testImplementation "androidx.test.ext:junit-ktx:1.1.2"
    testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:1.4.3"
    testImplementation 'com.nhaarman.mockitokotlin2:mockito-kotlin:2.2.0'
    testImplementation 'junit:junit:4.13.2'
    testImplementation 'androidx.test.espresso:espresso-core:3.3.0'
    testImplementation "org.robolectric:robolectric:4.5.1"
    testImplementation "org.mockito:mockito-android:2.28.2"

    // Testing Fragments
    debugImplementation "androidx.fragment:fragment-testing:1.3.2"

我用来寻找解决方案的链接“ 测试 LiveData 转换? https://jeroenmols.com/blog/2019/01/17/livedatajunit5/

4

1 回答 1

1

我在这里查看了您在 github 上的存储库。这是我发现的。

问题 1

您的第一个问题是您模拟了一个ViewModel. 因此,当您onResume为它进行模拟时,Fragment它会调用:

fun liveDataObserver() {
    viewModel.scoreLiveData.observe(viewLifecycleOwner, {
        // 
    } )
}

既然viewModel被嘲笑,scoreLiveData那么null你就会得到一个NPE。

要解决此问题,您还可以模拟出scoreLiveData方法,以便它返回一些可接受的结果:

...
    val liveData = MutableLiveData<Int>().apply { value = 3 }
    val viewModel = mock(MyViewModel::class.java)
    doReturn(liveData).`when`(viewModel).scoreLiveData
...

这将testOne完全解决您的问题,但还没有testTwo

问题 2

它仅与您的testTwo方法有关。问题是你liveDataObserver()在你的块中调用,并且在你被设置also之前被调用:Fragment's viewLifecycleOwneronCreateView

...
    scenario = launchFragmentInContainer {
        MyFragment(viewModel).also {
            it.liveDataObserver()
        }
    } 
...

我不确定您到底要在这里测试什么,但是如果您想验证是否可以Fragment's View在创建后开始观察,您可以执行以下操作:

    ...
    // make sure your Fragment is started
    scenario = launchFragmentInContainer (
        factory = MainFragmentFactory(viewModel),
        initialState = Lifecycle.State.STARTED
    )
    // call liveDataObserver on it
    scenario.withFragment {
        this.liveDataObserver()
    }

完整代码

@RunWith(RobolectricTestRunner::class) class MyFragmentTest { private lateinit var 场景:FragmentScenario

@Test
fun testOne() = runBlockingTest {
    val liveData = MutableLiveData<Int>().apply { value = 1 }
    val viewModel = mock(MyViewModel::class.java)
    doReturn(liveData).`when`(viewModel).scoreLiveData

    scenario = launchFragmentInContainer(
        factory = MainFragmentFactory(viewModel),
        fragmentArgs = null,
        themeResId = R.style.Theme_TDDScoreKeeper,
        initialState = Lifecycle.State.STARTED
    )

    scenario.moveToState(Lifecycle.State.RESUMED)
    scenario.recreate() // Simulates if the phone ran low on resources and the app had to be recreated.
}

@Test
fun testTwo() {
    val liveData = MutableLiveData<Int>().apply { value = 1 }
    val viewModel = mock(MyViewModel::class.java)
    doReturn(liveData).`when`(viewModel).scoreLiveData

    scenario = launchFragmentInContainer(
        factory = MainFragmentFactory(viewModel),
        initialState = Lifecycle.State.STARTED
    )
    scenario.withFragment {
        this.liveDataObserver()
    }
}

}

于 2021-04-16T06:54:13.703 回答