FragmentScenario не работает должным образом - PullRequest
0 голосов
/ 26 апреля 2020

Я использую почти ту же архитектуру, что и образец Google: GithubBrowserSample .

Это поле вставлено в мой класс фрагментов:

@Inject
lateinit var viewModelFactory: ViewModelProvider.Factory

val viewModel: TrainingListViewModel by viewModels {
    viewModelFactory
}

Во всех мои тесты фрагментов Я получил эту ошибку, которая соответствует полю, которое создается путем инъекции:

java.lang.RuntimeException: kotlin.UninitializedPropertyAccessException: lateinit property viewModelFactory has not been initialized
at androidx.test.runner.MonitoringInstrumentation.runOnMainSync(MonitoringInstrumentation.java:441)
at androidx.test.core.app.ActivityScenario.onActivity(ActivityScenario.java:564)
at androidx.fragment.app.testing.FragmentScenario.internalLaunch(FragmentScenario.java:300)
at androidx.fragment.app.testing.FragmentScenario.launchInContainer(FragmentScenario.java:282)
at com.maximesarrato.lafayapp.ui.training.TrainingListFragmentTest.init(TrainingListFragmentTest.kt:213)
at java.lang.reflect.Method.invoke(Native Method)
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 androidx.test.internal.runner.junit4.statement.RunBefores.evaluate(RunBefores.java:76)
at org.junit.rules.TestWatcher$1.evaluate(TestWatcher.java:55)
at org.junit.rules.TestWatcher$1.evaluate(TestWatcher.java:55)
at org.junit.rules.TestWatcher$1.evaluate(TestWatcher.java:55)
at org.junit.rules.RunRules.evaluate(RunRules.java:20)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
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.junit.runners.ParentRunner.run(ParentRunner.java:363)
at androidx.test.ext.junit.runners.AndroidJUnit4.run(AndroidJUnit4.java:104)
at org.junit.runners.Suite.runChild(Suite.java:128)
at org.junit.runners.Suite.runChild(Suite.java:27)
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.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at org.junit.runner.JUnitCore.run(JUnitCore.java:115)
at androidx.test.internal.runner.TestExecutor.execute(TestExecutor.java:56)
at androidx.test.runner.AndroidJUnitRunner.onStart(AndroidJUnitRunner.java:392)
at android.app.Instrumentation$InstrumentationThread.run(Instrumentation.java:2189)
Caused by: kotlin.UninitializedPropertyAccessException: lateinit property viewModelFactory has not been initialized
at com.maximesarrato.lafayapp.ui.training.TrainingListFragment.getViewModelFactory(TrainingListFragment.kt:41)
at com.maximesarrato.lafayapp.ui.training.TrainingListFragment$viewModel$2.invoke(TrainingListFragment.kt:49)
at com.maximesarrato.lafayapp.ui.training.TrainingListFragment$viewModel$2.invoke(TrainingListFragment.kt:39)
at androidx.lifecycle.ViewModelLazy.getValue(ViewModelProvider.kt:52)
at androidx.lifecycle.ViewModelLazy.getValue(ViewModelProvider.kt:41)
at com.maximesarrato.lafayapp.ui.training.TrainingListFragment.getViewModel(Unknown Source:7)
at com.maximesarrato.lafayapp.ui.training.TrainingListFragmentTest$init$$inlined$launchFragmentInContainer$1.instantiate(FragmentScenario.kt:114)
at androidx.fragment.app.testing.FragmentScenario$1.perform(FragmentScenario.java:310)
at androidx.fragment.app.testing.FragmentScenario$1.perform(FragmentScenario.java:301)
at androidx.test.core.app.ActivityScenario.lambda$onActivity$2$ActivityScenario(ActivityScenario.java:551)
at androidx.test.core.app.ActivityScenario$$Lambda$4.run(Unknown Source:4)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:462)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at android.app.Instrumentation$SyncRunnable.run(Instrumentation.java:2207)
at android.os.Handler.handleCallback(Handler.java:883)
at android.os.Handler.dispatchMessage(Handler.java:100)
at android.os.Looper.loop(Looper.java:214)
at android.app.ActivityThread.main(ActivityThread.java:7356)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)

Это происходит, когда я устанавливаю свой фрагмент следующим образом с FragmentScenario:

viewModel = mock(TrainingListViewModel::class.java)

val scenario = launchFragmentInContainer(themeResId = R.style.Theme_LafayWorkbook) {
    TrainingListFragment().apply {
        appExecutors = countingAppExecutors.appExecutors
        viewModelFactory = ViewModelUtil.createFor(viewModel)
    }
}

dataBindingIdlingResourceRule.monitorFragment(scenario)
scenario.onFragment { fragment ->
    Navigation.setViewNavController(fragment.requireView(), navController)
    fragment.binding.trainingListRv.itemAnimator = null
    fragment.disableProgressBarAnimations()
}

Но когда Я настроил фрагмент перед использованием его с FragmentScenario, он работает:

viewModel = mock(TrainingListViewModel::class.java)
val trainingListFragment = TrainingListFragment()
trainingListFragment.viewModelFactory = ViewModelUtil.createFor(viewModel)
trainingListFragment.appExecutors = countingAppExecutors.appExecutors

trainingListFragment.viewLifecycleOwnerLiveData.observeForever { viewLifecycleOwner ->
    if (viewLifecycleOwner != null) {
        Navigation.setViewNavController(trainingListFragment.requireView(), navController)
    }
}

val scenario = launchFragmentInContainer(themeResId = R.style.Theme_LafayWorkbook) {
    trainingListFragment
}

Вот исходный код моего теста:

@RunWith(AndroidJUnit4::class)
class TrainingListFragmentTest {
    @Rule
    @JvmField
    val executorRule = TaskExecutorWithIdlingResourceRule()

    @Rule
    @JvmField
    val countingAppExecutors = CountingAppExecutorsRule()

    @Rule
    @JvmField
    val dataBindingIdlingResourceRule = DataBindingIdlingResourceRule()

    private val navController = mock<NavController>()
    private val trainingsLiveData = MutableLiveData<Resource<List<Training>>>()
    private val isPremiumLiveData = MutableLiveData<PremiumAccount>()
    private val trainingDeletedLiveData = MutableLiveData<Event<Boolean>>()
    private val navigateToCreateTrainingLiveData = MutableLiveData<Event<Boolean>>()
    private val navigateToExerciseListLiveData = MutableLiveData<Event<Training>>()
    private lateinit var viewModel: TrainingListViewModel

    @Before
    fun init() {
        viewModel = mock(TrainingListViewModel::class.java)
        val trainingListFragment = TrainingListFragment()
        trainingListFragment.viewModelFactory = ViewModelUtil.createFor(viewModel)
        trainingListFragment.appExecutors = countingAppExecutors.appExecutors

        trainingListFragment.viewLifecycleOwnerLiveData.observeForever { viewLifecycleOwner ->
            if (viewLifecycleOwner != null) {
                Navigation.setViewNavController(trainingListFragment.requireView(), navController)
            }
        }

        `when`(viewModel.trainingsLiveData).thenReturn(trainingsLiveData)
        `when`(viewModel.isPremium).thenReturn(isPremiumLiveData)
        `when`(viewModel.trainingDeleted).thenReturn(trainingDeletedLiveData)
        `when`(viewModel.navigateToCreateTraining).thenReturn(navigateToCreateTrainingLiveData)
        `when`(viewModel.navigateToExerciseList).thenReturn(navigateToExerciseListLiveData)

        val scenario = launchFragmentInContainer(themeResId = R.style.Theme_LafayWorkbook) {
            trainingListFragment
        }
        dataBindingIdlingResourceRule.monitorFragment(scenario)
        scenario.onFragment { fragment ->
            Navigation.setViewNavController(fragment.requireView(), navController)
            fragment.binding.trainingListRv.itemAnimator = null
            fragment.disableProgressBarAnimations()
        }
    }

}

У вас, ребята, есть представление о том, почему это происходит? и как я могу это исправить? Я действительно борюсь с этим: (

1 Ответ

0 голосов
/ 27 апреля 2020

Проблема, с которой вы столкнулись, связана с изменением теней .

Когда вы пишете этот код

viewModel = mock(TrainingListViewModel::class.java)

val scenario = launchFragmentInContainer(themeResId = R.style.Theme_LafayWorkbook) {
    TrainingListFragment().apply {
        appExecutors = countingAppExecutors.appExecutors
        viewModelFactory = ViewModelUtil.createFor(viewModel)
    }
}

ViewModelUtil.createFor(viewModel) означает , а не используя переменную viewModel, которую вы создали с помощью mock выше, но вместо этого используете переменную viewModel ** в вашем TrainingListFragment. Это потому, что вы используете apply { }, что означает, что область, к которой применяется код, является самим фрагментом - фактически, как если бы ваш код был внутри этого фрагмента, поэтому viewModel действительно TrainingListViewModel.this.viewModel.

Вот почему в вашем сообщении об ошибке написано

at com.maximesarrato.lafayapp.ui.training.TrainingListFragment.getViewModel(Unknown Source:7)
at com.maximesarrato.lafayapp.ui.training.TrainingListFragmentTest$init$$inlined$launchFragmentInContainer$1.instantiate(FragmentScenario.kt:114)

Вы звоните getViewModel(), т.е. получаете доступ к свойству viewModel из вашего блока instantiate.

Самый простой Решение состоит в том, чтобы просто использовать другое имя переменной для вашей фиктивной viewModel вместо того же имени viewModel. Если бы оно было названо mockViewModel, то при вызове ViewModelUtil.createFor(mockViewModel) будет правильно сослаться на вашу фиктивную ViewModel, поскольку в вашем фрагменте не будет никакой переменной с таким же именем.

Другой вариант - использовать also вместо apply:

viewModel = mock(TrainingListViewModel::class.java)

val scenario = launchFragmentInContainer(themeResId = R.style.Theme_LafayWorkbook) {
    TrainingListFragment().also { fragment ->
        fragment.appExecutors = countingAppExecutors.appExecutors
        fragment.viewModelFactory = ViewModelUtil.createFor(viewModel)
    }
}

also, как и apply, всегда возвращает исходный объект - ваш TrainingListFragment (это правильно и то, что вы хотите вернуть).

Однако при использовании also вам нужно назвать переменную, с которой вы работаете (здесь я использую fragment вместо значения по умолчанию it), и вам нужно специально использовать fragment.viewModel, если вы хотите сослаться к чему-то внутри переменной фрагмента. Опять же, без тени имени, в этом случае viewModel будет указывать на единственную viewModel переменную в области видимости - вашу ложную ViewModel.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...