片段测试:只有创建视图层次结构的原始线程才能触摸其视图

问题描述 投票:0回答:1

我在一段时间内苦苦挣扎,所以我决定在这里寻求帮助...我使用的架构几乎与Google示例相同:GithubBrowserSample

在测试我的一个片段(androidTest)时遇到此错误:

android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:8191)
at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:1420)
at android.view.View.requestLayout(View.java:24454)
at android.view.View.requestLayout(View.java:24454)
at android.view.View.requestLayout(View.java:24454)
at android.view.View.requestLayout(View.java:24454)
at androidx.constraintlayout.widget.ConstraintLayout.requestLayout(ConstraintLayout.java:3028)
at android.view.View.requestLayout(View.java:24454)
at androidx.recyclerview.widget.RecyclerView.requestLayout(RecyclerView.java:4412)
at androidx.recyclerview.widget.RecyclerView$RecyclerViewDataObserver.triggerUpdateProcessor(RecyclerView.java:5582)
at androidx.recyclerview.widget.RecyclerView$RecyclerViewDataObserver.onItemRangeInserted(RecyclerView.java:5557)
at androidx.recyclerview.widget.RecyclerView$AdapterDataObservable.notifyItemRangeInserted(RecyclerView.java:12278)
at androidx.recyclerview.widget.RecyclerView$Adapter.notifyItemRangeInserted(RecyclerView.java:7498)
at androidx.recyclerview.widget.AdapterListUpdateCallback.onInserted(AdapterListUpdateCallback.java:42)
at androidx.recyclerview.widget.AsyncListDiffer.submitList(AsyncListDiffer.java:283)
at androidx.recyclerview.widget.AsyncListDiffer.submitList(AsyncListDiffer.java:231)
at androidx.recyclerview.widget.ListAdapter.submitList(ListAdapter.java:128)
at com.maximesarrato.lafayapp.ui.training.TrainingListFragment$onViewCreated$3.onChanged(TrainingListFragment.kt:112)
at com.maximesarrato.lafayapp.ui.training.TrainingListFragment$onViewCreated$3.onChanged(TrainingListFragment.kt:39)
at androidx.lifecycle.LiveData.considerNotify(LiveData.java:131)
at androidx.lifecycle.LiveData.dispatchingValue(LiveData.java:149)
at androidx.lifecycle.LiveData.setValue(LiveData.java:307)
at androidx.lifecycle.MutableLiveData.setValue(MutableLiveData.java:50)
at androidx.lifecycle.LiveData$1.run(LiveData.java:91)
at androidx.arch.core.executor.testing.InstantTaskExecutorRule$1.postToMainThread(InstantTaskExecutorRule.java:43)
at androidx.arch.core.executor.ArchTaskExecutor.postToMainThread(ArchTaskExecutor.java:101)
at androidx.lifecycle.LiveData.postValue(LiveData.java:291)
at androidx.lifecycle.MutableLiveData.postValue(MutableLiveData.java:45)
at com.maximesarrato.lafayapp.ui.training.TrainingListFragmentTest.testTrainingListLoaded(TrainingListFragmentTest.kt:119)
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 org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at androidx.test.internal.runner.junit4.statement.RunBefores.evaluate(RunBefores.java:80)
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.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)

当我向ListAdapter提交数据时,此部分代码会发生此错误:

viewModel.trainingsLiveData.observe(viewLifecycleOwner, Observer { result ->
    result?.data?.let {
        // Error occurs here
        adapter.submitList(it)
    }
})

仅当列表不为空时才会发生此错误。使用应用程序时,我没有遇到任何错误,但仅在测试期间。

这是我的测试的源代码:

@RunWith(AndroidJUnit4::class)
class TrainingListFragmentTest {
    @get:Rule
    var instantTaskExecutorRule = InstantTaskExecutorRule()

    @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()
        }
    }

    /**
     * Verify that when data is loading then Progress bar is displayed
     * and Helper text is not displayed.
     *
     */
    @Test
    // Works
    fun testLoading() {
        trainingsLiveData.postValue(Resource.loading(null))
        onView(withId(R.id.trainingProgressBar)).check(matches(isDisplayed()))
        onView(withId(R.id.trainingHelperTextView)).check(matches(not(isDisplayed())))
    }

    /**
     * Verify that when data is loaded and correspond to an empty list then Progress
     * bar is not displayed and Helper text is displayed.
     *
     */
    @Test
    // Works
    fun testEmptyListLoaded() {
        trainingsLiveData.postValue(Resource.success(listOf()))
        onView(withId(R.id.trainingProgressBar)).check(matches(not(isDisplayed())))
        onView(withId(R.id.trainingHelperTextView)).check(matches(isDisplayed()))
    }

    /**
     * Checks helper text disappears and list of Training is correctly displayed
     *
     */
    @Test
    // This test doesn't work :(
    fun testTrainingListLoaded() {
        val trainings = createTrainings(
            3,
            createExercises(2)
        )
        trainingsLiveData.postValue(Resource.success(trainings.toMutableList()))
        onView(withId(R.id.trainingHelperTextView)).check(matches(not(isDisplayed())))
        onView(listMatcher().atPosition(0)).check(matches(hasDescendant(withText("Level 1"))))
    }
}

你们对为什么会这样以及我如何解决这个问题有想法吗?

android android-fragments android-livedata android-architecture-components android-fragmentscenario
1个回答
0
投票

因为您直接设置该值。像其他测试一样使用postValue

    fun testTrainingListLoaded() {
        val trainings = createTrainings(
            3,
            createExercises(2)
        )
        trainingsLiveData.postValue(Resource.success(trainings.toMutableList()))
        onView(withId(R.id.trainingHelperTextView)).check(matches(not(isDisplayed())))
        onView(listMatcher().atPosition(0)).check(matches(hasDescendant(withText("Level 1"))))
    }

要查看postValuesetValue有什么区别,您可以检查this

总而言之,关键区别在于:

setValue()方法必须从主线程调用。但是如果你需要从后台线程设置一个值,应使用postValue()。

© www.soinside.com 2019 - 2024. All rights reserved.