【问题标题】:Fragment Unit Testing: launchFragment throws ClassCastException片段单元测试:launchFragment 抛出 ClassCastException
【发布时间】:2019-10-31 20:57:32
【问题描述】:

我试图在我的单元测试中调用 Fragment 类中的方法,但我不断收到错误 java.lang.ClassCastException: androidx.fragment.app.testing.FragmentScenario$EmptyFragmentActivity cannot be cast to com.nu.rms.inspections.ui.activity.InspectionActivity

我正在关注 Google 的 docs。我很困惑为什么空片段活动有一个演员试图成为我的 InspectionActivity(片段所在的父活动),也许这是意料之中的?

我可以做些什么来缓解 CastClassException 并在我的单元测试中使用我的片段的方法? (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)

【问题讨论】:

    标签: android unit-testing android-fragments testing android-fragmentactivity


    【解决方案1】:

    FragmentScenario 将您的 Fragment 添加到一个空的活动类 - 堆栈跟踪中提到的 androidx.fragment.app.testing.FragmentScenario$EmptyFragmentActivity

    这意味着您的 Fragment 不是在您的 InspectionActivity 类的实例中。您正在崩溃,因为您的 clearRFIDCache() 方法将活动转换为 InspectionActivity

    如果您想在特定的 Activity 实例中测试您的 Fragment 并在两者之间建立强耦合,您需要使用 ActivityScenario 并将您的 Fragment 手动添加到该 Activity,而不是使用 FragmentScenario让您无法控制正在使用的活动类。

    理想情况下,您不应该将 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 参数,允许您注入测试接口,确保您可以检查是否获得了预期的回调,而无需依赖活动的特定子类。使用 Kotlin 时,您还可以使用尾随的 lambda 语法来构造 Fragment 并完全跳过手动创建工厂:

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

    【讨论】:

    • 我添加了一个示例界面FragmentFactory,以及您希望如何更改活动和测试以利用它们。你只想用你需要的任何方法来填充你的界面。
    • 感谢您的详细示例!我通过使用接口将我的 Activity 和 Fragment 解耦,但我选择不使用 Factory。使用工厂有什么好处?是否只是为了让我们的接口回调可以通过 launchFragment() 方法进行测试?
    • 我猜当你不直接实例化你的 Fragment 例如 viewpager 时,fragmentfactory 方法会派上用场。
    猜你喜欢
    • 2019-01-29
    • 2017-08-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-08-10
    相关资源
    最近更新 更多