片段单元测试:launchFragment抛出ClassCastException [英] Fragment Unit Testing: launchFragment throws ClassCastException
问题描述
我正在单元测试中尝试在Fragment类中调用方法,但我不断收到错误java.lang.ClassCastException: androidx.fragment.app.testing.FragmentScenario$EmptyFragmentActivity cannot be cast to com.nu.rms.inspections.ui.activity.InspectionActivity
I'm trying to call methods within my Fragment class in my unit test but I keep getting the error java.lang.ClassCastException: androidx.fragment.app.testing.FragmentScenario$EmptyFragmentActivity cannot be cast to com.nu.rms.inspections.ui.activity.InspectionActivity
我正在关注Google的文档.我感到困惑,为什么空片段活动试图将其强制转换为我的InspectionActivity(片段所在的父活动),也许这是预期的?
I'm following Google's docs. I'm confused why the empty fragment activity is having a cast attempted to become my InspectionActivity (the parent activity the fragment resides in), perhaps this is expected?
我该如何缓解CastClassException并在单元测试中使用片段的方法? (无法解决我问题的相关问题)
What can I do to alleviate the CastClassException and use my fragment's methods within my unit test? (related question that doesn't solve my issue)
测试
@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
}
}
...
}
片段类
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$1.onChanged(ContentFragment.kt:77)
at com.nu.rms.inspections.ui.fragment.ContentFragment$onViewCreated$1.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$2$1.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$2.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$1.perform(FragmentScenario.java:312)
at androidx.fragment.app.testing.FragmentScenario$1.perform(FragmentScenario.java:291)
at androidx.test.core.app.ActivityScenario.lambda$onActivity$1$ActivityScenario(ActivityScenario.java:534)
at androidx.test.core.app.ActivityScenario$$Lambda$0.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$1.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$1.evaluate(RobolectricTestRunner.java:600)
at org.robolectric.internal.SandboxTestRunner$2.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$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.robolectric.internal.SandboxTestRunner$1.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
将您的Fragment添加到一个空的活动类-堆栈跟踪中提到的androidx.fragment.app.testing.FragmentScenario$EmptyFragmentActivity
.
FragmentScenario
adds your Fragment to an empty activity class - the androidx.fragment.app.testing.FragmentScenario$EmptyFragmentActivity
mentioned in the stack trace.
这意味着您的片段在InspectionActivity
类的实例中不是.您崩溃了,因为您的clearRFIDCache()
方法将活动投射到了InspectionActivity
.
That means that your Fragment is not in an instance of your InspectionActivity
class. You're getting a crash because your clearRFIDCache()
method is casting the activity to InspectionActivity
.
如果要在Activity的特定实例中测试Fragment并在两者之间建立牢固的耦合,则需要使用ActivityScenario
并手动将Fragment添加到该Activity中,而不是使用FragmentScenario
无法控制您正在使用的活动类.
If you want to need to test your Fragment within a specific instance of Activity and have a strong coupling between the two, you need to use ActivityScenario
and manually add your Fragment to that Activity, rather than using FragmentScenario
that gives you no control over the activity class you're using.
理想情况下,您不应将片段与活动紧密结合.例如,您可以提供一个FragmentFactory
,该FragmentFactory
使用构造函数注入来添加您的Fragment所需的接口,而不是您的Fragment到达Activity来直接调用方法,如
Ideally, you shouldn't tightly couple your Fragment to your Activity. For example, you can provide a FragmentFactory
that uses constructor injection to add the interface your Fragment requires, rather than your Fragment reaching up to the Activity to call methods directly as discussed in the Fragments: Past, Present, and Future talk:
// 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
参数,该参数允许您注入测试接口,从而确保您可以在不依赖于活动的特定子类的情况下,检查是否可以获取所需的回调.使用Kotlin时,您还可以使用尾随lambda语法构造Fragment并完全跳过手动创建Factory的过程:
launchFragment
takes a factory
parameter that allows you to inject a test interface, ensuring that you can check that you get the callbacks you expect without relying on a specific subclass of your activity. When using Kotlin, you can also use the trailing lambda syntax to construct the Fragment and skip manually creating a Factory at all:
@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
}
}
...
}
这篇关于片段单元测试:launchFragment抛出ClassCastException的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!