0

我挣扎了一段时间,所以我决定在这里寻求帮助......我使用的架构几乎与谷歌示例相同: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"))))
    }
}

你们知道为什么会发生这种情况以及我该如何解决吗?

4

2 回答 2

1

此问题链接到:FragmentScenario 没有按预期工作,已成功解决!

我不应该使用规则 IntantTaskExecutorRule:

@get:Rule
var instantTaskExecutorRule = InstantTaskExecutorRule()

事实上,如果没有这条规则,我应该使用postValue而不是setValue像穆罕默德所说的那样。此外,正如我提出并链接到这篇文章的另一个问题中提到的,主要问题与变量阴影有关:https ://stackoverflow.com/a/61449895/9578385

谢谢你的帮助!

于 2020-04-30T07:11:22.383 回答
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()。

于 2020-04-24T19:35:03.720 回答