Robolectric 开始一个有观察者的片段
Robolectric start a fragment that has an observer
如何在 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 实现阴影 class。使用 Looper 模式注释 class。
@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"
我用来寻找解决方案的链接'
Testing LiveData Transformations?
https://jeroenmols.com/blog/2019/01/17/livedatajunit5/
我查看了您在 github here 上的存储库。这是我的发现。
问题 1
你的第一个问题是你模拟了一个ViewModel
。因此,当您为 Fragment
模拟 onResume
时,它会调用:
fun liveDataObserver() {
viewModel.scoreLiveData.observe(viewLifecycleOwner, {
//
} )
}
由于 viewModel
是模拟的,scoreLiveData
是 null
并且你得到一个 NPE。
要解决此问题,您还要模拟 scoreLiveData
方法,以便它 returns 一些可接受的结果:
...
val liveData = MutableLiveData<Int>().apply { value = 3 }
val viewModel = mock(MyViewModel::class.java)
doReturn(liveData).`when`(viewModel).scoreLiveData
...
这将完全修复您的 testOne
,但尚未 testTwo
。
问题 2
它只与您的 testTwo
方法有关。问题是您在 also
块中调用 liveDataObserver()
,并且在 Fragment's
viewLifecycleOwner
已在 onCreateView
中设置之前被调用:
...
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我的片段测试{
私有 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()
}
}
}
如何在 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 实现阴影 class。使用 Looper 模式注释 class。
@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"
我用来寻找解决方案的链接' Testing LiveData Transformations? https://jeroenmols.com/blog/2019/01/17/livedatajunit5/
我查看了您在 github here 上的存储库。这是我的发现。
问题 1
你的第一个问题是你模拟了一个ViewModel
。因此,当您为 Fragment
模拟 onResume
时,它会调用:
fun liveDataObserver() {
viewModel.scoreLiveData.observe(viewLifecycleOwner, {
//
} )
}
由于 viewModel
是模拟的,scoreLiveData
是 null
并且你得到一个 NPE。
要解决此问题,您还要模拟 scoreLiveData
方法,以便它 returns 一些可接受的结果:
...
val liveData = MutableLiveData<Int>().apply { value = 3 }
val viewModel = mock(MyViewModel::class.java)
doReturn(liveData).`when`(viewModel).scoreLiveData
...
这将完全修复您的 testOne
,但尚未 testTwo
。
问题 2
它只与您的 testTwo
方法有关。问题是您在 also
块中调用 liveDataObserver()
,并且在 Fragment's
viewLifecycleOwner
已在 onCreateView
中设置之前被调用:
...
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我的片段测试{ 私有 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()
}
}
}