Android 机器人分子启动一个有观察者的片段

Android 机器人分子启动一个有观察者的片段,android,android-livedata,robolectric,android-unit-testing,android-fragmentscenario,Android,Android Livedata,Robolectric,Android Unit Testing,Android Fragmentscenario,如何使用Robolectric在测试范围内使用LiveData observer启动片段 碎片 class MyFragment(private val viewModel: MyViewModel) : Fragment() { ... fun myObserver { ... // If I remove this observer the test will pass. viewModel.MyLiveData.obs

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

碎片

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

    ...

    fun myObserver {
        ... 
        // If I remove this observer the test will pass. 
        viewModel.MyLiveData.observe(viewLifecycleOwner, Observer{
            ...
        }
    }
}
我的测试使用RobolectrictTestRunner,因此我可以在测试范围内启动片段

@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. 

    } 
}
我在尝试运行测试时遇到以下错误。在实例化碎片场景之前和之后,我尝试将主循环器设置为空闲

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.

我试过以下方法

  • 为主循环器实现阴影类。使用活套模式注释类
  • 添加场景状态
我的测试依赖性

    // 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"
我用来寻找解决方案的链接'

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

问题1 第一个问题是模拟了一个
ViewModel
。因此,当您为
片段模拟
onResume
时,它会调用:

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
...
我不确定您到底想测试什么,但如果您想验证是否可以在创建
片段的
视图后开始观察,您可以执行以下操作:

    ...
    // make sure your Fragment is started
    scenario = launchFragmentInContainer (
        factory = MainFragmentFactory(viewModel),
        initialState = Lifecycle.State.STARTED
    )
    // call liveDataObserver on it
    scenario.withFragment {
        this.liveDataObserver()
    }
完整代码 @RunWith(RobolectrictTestRunner::类) 类MyFragmentTest{ 私有lateinit变量场景:碎片场景

@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()
    }
}
@测试
fun testOne()=运行BlockingTest{
val liveData=MutableLiveData().apply{value=1}
val viewModel=mock(MyViewModel::class.java)
doReturn(liveData)。`when`(viewModel).scoreLiveData
场景=启动FragmentInContainer(
factory=MainFragmentFactory(视图模型),
fragmentArgs=null,
themeResId=R.style.Theme\u TDDScoreKeeper,
initialState=Lifecycle.State.STARTED
)
scenario.moveToState(Lifecycle.State.Resume)
scenario.recreate()//模拟手机资源不足时是否必须重新创建应用程序。
}
@试验
趣味测试二{
val liveData=MutableLiveData().apply{value=1}
val viewModel=mock(MyViewModel::class.java)
doReturn(liveData)。`when`(viewModel).scoreLiveData
场景=启动FragmentInContainer(
factory=MainFragmentFactory(视图模型),
initialState=Lifecycle.State.STARTED
)
scenario.withFragment{
this.liveDataObserver()
}
}

}

让我们忽略测试二,因为那只是我在尝试不同的事情。如果我使用一个具有模拟依赖项的真实ViewModel来测试片段,那么这不会将测试从单元测试更改为集成测试吗?模拟ViewModel不是为了避免创建具有依赖关系的真实ViewModel吗?是的,在这种情况下,最好完全模拟
ViewModel
。我更新了我的答案。酷,这让我成功了。虽然我不确定我是否理解您何时使用doReturn when vs when then。@Shawn Cool!在您的情况下(如果使用
mock
对象),选择哪一对并不重要。不过,如果您使用
spy
方法,这很重要。。
...
    scenario = launchFragmentInContainer {
        MyFragment(viewModel).also {
            it.liveDataObserver()
        }
    } 
...
    ...
    // make sure your Fragment is started
    scenario = launchFragmentInContainer (
        factory = MainFragmentFactory(viewModel),
        initialState = Lifecycle.State.STARTED
    )
    // call liveDataObserver on it
    scenario.withFragment {
        this.liveDataObserver()
    }
@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()
    }
}