片段单元测试:launchFragment 抛出 ClassCastException
Fragment Unit Testing: launchFragment throws ClassCastException
我试图在我的单元测试中调用片段 class 中的方法,但我一直收到错误 java.lang.ClassCastException: androidx.fragment.app.testing.FragmentScenario$EmptyFragmentActivity cannot be cast to com.nu.rms.inspections.ui.activity.InspectionActivity
我正在关注 Google 的 docs。我很困惑为什么空片段 activity 试图转换为我的 InspectionActivity(片段所在的父 activity),也许这是预期的?
如何减轻 CastClassException 并在我的单元测试中使用我的片段方法? ()
测试
@RunWith(AndroidJUnit4::class)
class ExampleUnitTest {
@Test
fun `inspection failure point to location mapping is correct`() {
val scenario = launchFragment<ContentFragment>()
scenario.onFragment { fragment ->
//TODO: test logic
}
}
...
}
片段Class
import androidx.fragment.app.Fragment
...
class ContentFragment : Fragment() {...}
堆栈跟踪
java.lang.ClassCastException: androidx.fragment.app.testing.FragmentScenario$EmptyFragmentActivity cannot be cast to com.nu.rms.inspections.ui.activity.InspectionActivity
at com.nu.rms.inspections.ui.fragment.ContentFragment.clearRFIDCache(ContentFragment.kt:565)
at com.nu.rms.inspections.ui.fragment.ContentFragment.showStep1(ContentFragment.kt:222)
at com.nu.rms.inspections.ui.fragment.ContentFragment.access$showStep1(ContentFragment.kt:37)
at com.nu.rms.inspections.ui.fragment.ContentFragment$onViewCreated.onChanged(ContentFragment.kt:77)
at com.nu.rms.inspections.ui.fragment.ContentFragment$onViewCreated.onChanged(ContentFragment.kt:37)
at androidx.lifecycle.LiveData.considerNotify(LiveData.java:113)
at androidx.lifecycle.LiveData.dispatchingValue(LiveData.java:131)
at androidx.lifecycle.LiveData.setValue(LiveData.java:289)
at androidx.lifecycle.MutableLiveData.setValue(MutableLiveData.java:33)
at androidx.lifecycle.Transformations.onChanged(Transformations.java:153)
at androidx.lifecycle.MediatorLiveData$Source.onChanged(MediatorLiveData.java:152)
at androidx.lifecycle.LiveData.considerNotify(LiveData.java:113)
at androidx.lifecycle.LiveData.dispatchingValue(LiveData.java:126)
at androidx.lifecycle.LiveData$ObserverWrapper.activeStateChanged(LiveData.java:424)
at androidx.lifecycle.LiveData.observeForever(LiveData.java:214)
at androidx.lifecycle.MediatorLiveData$Source.plug(MediatorLiveData.java:141)
at androidx.lifecycle.MediatorLiveData.addSource(MediatorLiveData.java:96)
at androidx.lifecycle.Transformations.onChanged(Transformations.java:150)
at androidx.lifecycle.MediatorLiveData$Source.onChanged(MediatorLiveData.java:152)
at androidx.lifecycle.LiveData.considerNotify(LiveData.java:113)
at androidx.lifecycle.LiveData.dispatchingValue(LiveData.java:126)
at androidx.lifecycle.LiveData$ObserverWrapper.activeStateChanged(LiveData.java:424)
at androidx.lifecycle.LiveData.observeForever(LiveData.java:214)
at androidx.lifecycle.MediatorLiveData$Source.plug(MediatorLiveData.java:141)
at androidx.lifecycle.MediatorLiveData.onActive(MediatorLiveData.java:118)
at androidx.lifecycle.LiveData$ObserverWrapper.activeStateChanged(LiveData.java:418)
at androidx.lifecycle.LiveData$LifecycleBoundObserver.onStateChanged(LiveData.java:376)
at androidx.lifecycle.LifecycleRegistry$ObserverWithState.dispatchEvent(LifecycleRegistry.java:361)
at androidx.lifecycle.LifecycleRegistry.forwardPass(LifecycleRegistry.java:300)
at androidx.lifecycle.LifecycleRegistry.sync(LifecycleRegistry.java:339)
at androidx.lifecycle.LifecycleRegistry.moveToState(LifecycleRegistry.java:145)
at androidx.lifecycle.LifecycleRegistry.handleLifecycleEvent(LifecycleRegistry.java:131)
at androidx.fragment.app.FragmentViewLifecycleOwner.handleLifecycleEvent(FragmentViewLifecycleOwner.java:51)
at androidx.fragment.app.Fragment.performStart(Fragment.java:2639)
at androidx.fragment.app.FragmentManagerImpl.moveToState(FragmentManagerImpl.java:915)
at androidx.fragment.app.FragmentManagerImpl.moveFragmentToExpectedState(FragmentManagerImpl.java:1238)
at androidx.fragment.app.FragmentManagerImpl.moveToState(FragmentManagerImpl.java:1303)
at androidx.fragment.app.BackStackRecord.executeOps(BackStackRecord.java:439)
at androidx.fragment.app.FragmentManagerImpl.executeOps(FragmentManagerImpl.java:2079)
at androidx.fragment.app.FragmentManagerImpl.executeOpsTogether(FragmentManagerImpl.java:1869)
at androidx.fragment.app.FragmentManagerImpl.removeRedundantOperationsAndExecute(FragmentManagerImpl.java:1824)
at androidx.fragment.app.FragmentManagerImpl.execSingleAction(FragmentManagerImpl.java:1696)
at androidx.fragment.app.BackStackRecord.commitNow(BackStackRecord.java:293)
at androidx.fragment.app.testing.FragmentScenario.perform(FragmentScenario.java:312)
at androidx.fragment.app.testing.FragmentScenario.perform(FragmentScenario.java:291)
at androidx.test.core.app.ActivityScenario.lambda$onActivity$ActivityScenario(ActivityScenario.java:534)
at androidx.test.core.app.ActivityScenario$$Lambda[=12=].run(Unknown Source)
at org.robolectric.android.fakes.RoboMonitoringInstrumentation.runOnMainSync(RoboMonitoringInstrumentation.java:53)
at androidx.test.core.app.ActivityScenario.onActivity(ActivityScenario.java:527)
at androidx.fragment.app.testing.FragmentScenario.internalLaunch(FragmentScenario.java:290)
at androidx.fragment.app.testing.FragmentScenario.launch(FragmentScenario.java:203)
at com.nu.rms.inspections.ExampleUnitTest.inspection failure point to location mapping is correct(ExampleUnitTest.kt:56)
at org.junit.runners.model.FrameworkMethod.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.robolectric.RobolectricTestRunner$HelperTestRunner.evaluate(RobolectricTestRunner.java:600)
at org.robolectric.internal.SandboxTestRunner.evaluate(SandboxTestRunner.java:260)
at org.robolectric.internal.SandboxTestRunner.runChild(SandboxTestRunner.java:130)
at org.robolectric.internal.SandboxTestRunner.runChild(SandboxTestRunner.java:42)
at org.junit.runners.ParentRunner.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access[=12=]0(ParentRunner.java:58)
at org.junit.runners.ParentRunner.evaluate(ParentRunner.java:268)
at org.robolectric.internal.SandboxTestRunner.evaluate(SandboxTestRunner.java:84)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at androidx.test.runner.AndroidJUnit4.run(AndroidJUnit4.java:104)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
FragmentScenario
将您的片段添加到一个空 activity class - 堆栈跟踪中提到的 androidx.fragment.app.testing.FragmentScenario$EmptyFragmentActivity
。
这意味着您的 Fragment 在 InspectionActivity
class 的实例中是 而不是 。您正在崩溃,因为您的 clearRFIDCache()
方法正在将 activity 转换为 InspectionActivity
。
如果你想在 Activity 的特定实例中测试你的 Fragment 并且两者之间有很强的耦合,你需要使用 ActivityScenario
并手动将你的 Fragment 添加到那个 Activity,而不是使用 FragmentScenario
,这让您无法控制您正在使用的 activity class。
理想情况下,您不应该 将您的 Fragment 与您的 Activity 紧密耦合。例如,您可以提供一个 FragmentFactory
,它使用构造函数注入来添加您的 Fragment 所需的接口,而不是像 Fragments: Past, Present, and Future talk 中讨论的那样,您的 Fragment 达到 Activity 直接调用方法:
// Create an interface for what methods you want to expose
interface Inspector {
// whatever methods you want
}
// Change your Fragment to take in that interface
class ContentFragment(val inspector: Inspector) : Fragment() {
fun clearRFIDCache() {
// Now you can call methods on inspector here
// without casting your Activity
}
}
private class InspectionActivityFactory(
inspector: Inspector
) : FragmentFactory() {
override fun instantiate(
classLoader: ClassLoader,
className: String
) = when (className) {
ContentFragment::class.java.name -> ContentFragment(inspector)
else -> super.instantiate(classLoader, className)
}
}
// Now update your InspectionActivity to implement the interface
// and pass itself into an instance of the FragmentFactory you created
class InspectionActivity : AppCompatActivity(), Inspector {
override fun onCreate(savedInstanceState: Bundle?) {
supportFragmentManager.fragmentFactory =
InspectionActivityFactory(this)
super.onCreate(savedInstanceState)
...
}
}
launchFragment
接受一个 factory
参数,允许您注入一个测试接口,确保您可以检查是否获得了您期望的回调,而无需依赖特定的子class你的 activity。使用 Kotlin 时,您还可以使用尾随 lambda 语法来构造 Fragment,完全跳过手动创建 Factory:
@RunWith(AndroidJUnit4::class)
class ExampleUnitTest {
@Test
fun `inspection failure point to location mapping is correct`() {
val inspector = mock(Inspector::class.java)
val scenario = launchFragment {
ContentFragment(inspector)
}
scenario.onFragment { fragment ->
//TODO: test logic
}
}
...
}
我试图在我的单元测试中调用片段 class 中的方法,但我一直收到错误 java.lang.ClassCastException: androidx.fragment.app.testing.FragmentScenario$EmptyFragmentActivity cannot be cast to com.nu.rms.inspections.ui.activity.InspectionActivity
我正在关注 Google 的 docs。我很困惑为什么空片段 activity 试图转换为我的 InspectionActivity(片段所在的父 activity),也许这是预期的?
如何减轻 CastClassException 并在我的单元测试中使用我的片段方法? (
测试
@RunWith(AndroidJUnit4::class)
class ExampleUnitTest {
@Test
fun `inspection failure point to location mapping is correct`() {
val scenario = launchFragment<ContentFragment>()
scenario.onFragment { fragment ->
//TODO: test logic
}
}
...
}
片段Class
import androidx.fragment.app.Fragment
...
class ContentFragment : Fragment() {...}
堆栈跟踪
java.lang.ClassCastException: androidx.fragment.app.testing.FragmentScenario$EmptyFragmentActivity cannot be cast to com.nu.rms.inspections.ui.activity.InspectionActivity
at com.nu.rms.inspections.ui.fragment.ContentFragment.clearRFIDCache(ContentFragment.kt:565)
at com.nu.rms.inspections.ui.fragment.ContentFragment.showStep1(ContentFragment.kt:222)
at com.nu.rms.inspections.ui.fragment.ContentFragment.access$showStep1(ContentFragment.kt:37)
at com.nu.rms.inspections.ui.fragment.ContentFragment$onViewCreated.onChanged(ContentFragment.kt:77)
at com.nu.rms.inspections.ui.fragment.ContentFragment$onViewCreated.onChanged(ContentFragment.kt:37)
at androidx.lifecycle.LiveData.considerNotify(LiveData.java:113)
at androidx.lifecycle.LiveData.dispatchingValue(LiveData.java:131)
at androidx.lifecycle.LiveData.setValue(LiveData.java:289)
at androidx.lifecycle.MutableLiveData.setValue(MutableLiveData.java:33)
at androidx.lifecycle.Transformations.onChanged(Transformations.java:153)
at androidx.lifecycle.MediatorLiveData$Source.onChanged(MediatorLiveData.java:152)
at androidx.lifecycle.LiveData.considerNotify(LiveData.java:113)
at androidx.lifecycle.LiveData.dispatchingValue(LiveData.java:126)
at androidx.lifecycle.LiveData$ObserverWrapper.activeStateChanged(LiveData.java:424)
at androidx.lifecycle.LiveData.observeForever(LiveData.java:214)
at androidx.lifecycle.MediatorLiveData$Source.plug(MediatorLiveData.java:141)
at androidx.lifecycle.MediatorLiveData.addSource(MediatorLiveData.java:96)
at androidx.lifecycle.Transformations.onChanged(Transformations.java:150)
at androidx.lifecycle.MediatorLiveData$Source.onChanged(MediatorLiveData.java:152)
at androidx.lifecycle.LiveData.considerNotify(LiveData.java:113)
at androidx.lifecycle.LiveData.dispatchingValue(LiveData.java:126)
at androidx.lifecycle.LiveData$ObserverWrapper.activeStateChanged(LiveData.java:424)
at androidx.lifecycle.LiveData.observeForever(LiveData.java:214)
at androidx.lifecycle.MediatorLiveData$Source.plug(MediatorLiveData.java:141)
at androidx.lifecycle.MediatorLiveData.onActive(MediatorLiveData.java:118)
at androidx.lifecycle.LiveData$ObserverWrapper.activeStateChanged(LiveData.java:418)
at androidx.lifecycle.LiveData$LifecycleBoundObserver.onStateChanged(LiveData.java:376)
at androidx.lifecycle.LifecycleRegistry$ObserverWithState.dispatchEvent(LifecycleRegistry.java:361)
at androidx.lifecycle.LifecycleRegistry.forwardPass(LifecycleRegistry.java:300)
at androidx.lifecycle.LifecycleRegistry.sync(LifecycleRegistry.java:339)
at androidx.lifecycle.LifecycleRegistry.moveToState(LifecycleRegistry.java:145)
at androidx.lifecycle.LifecycleRegistry.handleLifecycleEvent(LifecycleRegistry.java:131)
at androidx.fragment.app.FragmentViewLifecycleOwner.handleLifecycleEvent(FragmentViewLifecycleOwner.java:51)
at androidx.fragment.app.Fragment.performStart(Fragment.java:2639)
at androidx.fragment.app.FragmentManagerImpl.moveToState(FragmentManagerImpl.java:915)
at androidx.fragment.app.FragmentManagerImpl.moveFragmentToExpectedState(FragmentManagerImpl.java:1238)
at androidx.fragment.app.FragmentManagerImpl.moveToState(FragmentManagerImpl.java:1303)
at androidx.fragment.app.BackStackRecord.executeOps(BackStackRecord.java:439)
at androidx.fragment.app.FragmentManagerImpl.executeOps(FragmentManagerImpl.java:2079)
at androidx.fragment.app.FragmentManagerImpl.executeOpsTogether(FragmentManagerImpl.java:1869)
at androidx.fragment.app.FragmentManagerImpl.removeRedundantOperationsAndExecute(FragmentManagerImpl.java:1824)
at androidx.fragment.app.FragmentManagerImpl.execSingleAction(FragmentManagerImpl.java:1696)
at androidx.fragment.app.BackStackRecord.commitNow(BackStackRecord.java:293)
at androidx.fragment.app.testing.FragmentScenario.perform(FragmentScenario.java:312)
at androidx.fragment.app.testing.FragmentScenario.perform(FragmentScenario.java:291)
at androidx.test.core.app.ActivityScenario.lambda$onActivity$ActivityScenario(ActivityScenario.java:534)
at androidx.test.core.app.ActivityScenario$$Lambda[=12=].run(Unknown Source)
at org.robolectric.android.fakes.RoboMonitoringInstrumentation.runOnMainSync(RoboMonitoringInstrumentation.java:53)
at androidx.test.core.app.ActivityScenario.onActivity(ActivityScenario.java:527)
at androidx.fragment.app.testing.FragmentScenario.internalLaunch(FragmentScenario.java:290)
at androidx.fragment.app.testing.FragmentScenario.launch(FragmentScenario.java:203)
at com.nu.rms.inspections.ExampleUnitTest.inspection failure point to location mapping is correct(ExampleUnitTest.kt:56)
at org.junit.runners.model.FrameworkMethod.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.robolectric.RobolectricTestRunner$HelperTestRunner.evaluate(RobolectricTestRunner.java:600)
at org.robolectric.internal.SandboxTestRunner.evaluate(SandboxTestRunner.java:260)
at org.robolectric.internal.SandboxTestRunner.runChild(SandboxTestRunner.java:130)
at org.robolectric.internal.SandboxTestRunner.runChild(SandboxTestRunner.java:42)
at org.junit.runners.ParentRunner.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access[=12=]0(ParentRunner.java:58)
at org.junit.runners.ParentRunner.evaluate(ParentRunner.java:268)
at org.robolectric.internal.SandboxTestRunner.evaluate(SandboxTestRunner.java:84)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at androidx.test.runner.AndroidJUnit4.run(AndroidJUnit4.java:104)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
FragmentScenario
将您的片段添加到一个空 activity class - 堆栈跟踪中提到的 androidx.fragment.app.testing.FragmentScenario$EmptyFragmentActivity
。
这意味着您的 Fragment 在 InspectionActivity
class 的实例中是 而不是 。您正在崩溃,因为您的 clearRFIDCache()
方法正在将 activity 转换为 InspectionActivity
。
如果你想在 Activity 的特定实例中测试你的 Fragment 并且两者之间有很强的耦合,你需要使用 ActivityScenario
并手动将你的 Fragment 添加到那个 Activity,而不是使用 FragmentScenario
,这让您无法控制您正在使用的 activity class。
理想情况下,您不应该 将您的 Fragment 与您的 Activity 紧密耦合。例如,您可以提供一个 FragmentFactory
,它使用构造函数注入来添加您的 Fragment 所需的接口,而不是像 Fragments: Past, Present, and Future talk 中讨论的那样,您的 Fragment 达到 Activity 直接调用方法:
// Create an interface for what methods you want to expose
interface Inspector {
// whatever methods you want
}
// Change your Fragment to take in that interface
class ContentFragment(val inspector: Inspector) : Fragment() {
fun clearRFIDCache() {
// Now you can call methods on inspector here
// without casting your Activity
}
}
private class InspectionActivityFactory(
inspector: Inspector
) : FragmentFactory() {
override fun instantiate(
classLoader: ClassLoader,
className: String
) = when (className) {
ContentFragment::class.java.name -> ContentFragment(inspector)
else -> super.instantiate(classLoader, className)
}
}
// Now update your InspectionActivity to implement the interface
// and pass itself into an instance of the FragmentFactory you created
class InspectionActivity : AppCompatActivity(), Inspector {
override fun onCreate(savedInstanceState: Bundle?) {
supportFragmentManager.fragmentFactory =
InspectionActivityFactory(this)
super.onCreate(savedInstanceState)
...
}
}
launchFragment
接受一个 factory
参数,允许您注入一个测试接口,确保您可以检查是否获得了您期望的回调,而无需依赖特定的子class你的 activity。使用 Kotlin 时,您还可以使用尾随 lambda 语法来构造 Fragment,完全跳过手动创建 Factory:
@RunWith(AndroidJUnit4::class)
class ExampleUnitTest {
@Test
fun `inspection failure point to location mapping is correct`() {
val inspector = mock(Inspector::class.java)
val scenario = launchFragment {
ContentFragment(inspector)
}
scenario.onFragment { fragment ->
//TODO: test logic
}
}
...
}