片段单元测试:launchFragment抛出ClassCastException [英] Fragment Unit Testing: launchFragment throws ClassCastException

查看:245
本文介绍了片段单元测试:launchFragment抛出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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆