Фрагментное тестирование фрагмента: launchFragment генерирует исключение ClassCastException - PullRequest
0 голосов
/ 31 октября 2019

Я пытаюсь вызвать методы в своем классе Fragment в моем модульном тесте, но получаю сообщение об ошибке java.lang.ClassCastException: androidx.fragment.app.testing.FragmentScenario$EmptyFragmentActivity cannot be cast to com.nu.rms.inspections.ui.activity.InspectionActivity

Я следую за документами Google от Google. Я сбит с толку, почему при выполнении операции пустого фрагмента была сделана попытка стать моей InspectionActivity (родительским действием, в котором находится фрагмент), возможно, это ожидаемо?

Что я могу сделать, чтобы смягчить 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
        }
    }
...
}

Класс фрагмента

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)

1 Ответ

2 голосов
/ 01 ноября 2019

FragmentScenario добавляет ваш Фрагмент в пустой класс активности - androidx.fragment.app.testing.FragmentScenario$EmptyFragmentActivity, упомянутый в трассировке стека.

Это означает, что ваш Фрагмент не в экземпляре вашего InspectionActivity класс. Вы получаете сбой, потому что ваш метод clearRFIDCache() преобразует действие в InspectionActivity.

Если вы хотите проверить свой фрагмент в конкретном экземпляре действия и иметь сильную связь между двумя, вам нужно использовать ActivityScenario и вручную добавить свой фрагмент к этому действию, а не FragmentScenario, который не дает вам никакого контроля над используемым вами классом деятельности.

В идеале вы не должны'1017 * тесно связывает ваш фрагмент с вашей деятельностью. Например, вы можете предоставить FragmentFactory, который использует инжекцию конструктора для добавления интерфейса, который требуется вашему Фрагменту, вместо того, чтобы ваш Фрагмент достигал Действия для прямого вызова методов, как обсуждалось в Фрагментах : прошлое, настоящее и будущее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 вы также можете использовать конечный лямбда-синтаксис для создания фрагмента и вообще пропустить создание фабрики:

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