使用Robolectric测试LiveData

4

我有一个测试,用于检查活动是否通过视图模型正确地从存储库获取数据。

@Config(application = TestApplication::class)
@RunWith(RobolectricTestRunner::class)
@LooperMode(LooperMode.Mode.PAUSED)
class BusinessTests {
    private lateinit var viewModel: BusinessCollectionViewModel
    private lateinit var activity: BusinessCollectionVerticalActivity
    private lateinit var observer: Observer<Triple<NetworkState, PagedList<Edge<Business>>, TimeTracking?>>

    @Before
    fun setUp() {
        observer = mock()
    }

    @Test
    fun givenBusinessMock_whenVerticalCollection_thenBusinessVerticalWith2Items() {

        val activityScenario = ActivityScenario.launch(BusinessCollectionVerticalActivity::class.java)
        activityScenario.onActivity {
            activity = it
        }

        viewModel = ViewModelProviders.of(activity)[BusinessCollectionViewModel::class.java]

        viewModel.data.observeForever(observer)
        assert(viewModel.data.value?.second?.size == 2)
    }
}

问题在于测试总是失败,但在调试时却通过了,但当我使用假条件进行断言调试时,会出现以下异常。

java.lang.Exception: Main looper has queued unexecuted runnables. This might be the cause of the test failure. You might need a shadowOf(getMainLooper()).idle() call.

这是一种非常奇怪的行为,我不知道该怎么办。当然,在观察之前我尝试过添加shadowOf(getMainLooper()).idle()。

我正在使用最新的robolectric 4.3版本,这可能是一个bug吗?

2个回答

5

我曾有相同的问题,通过使用instantTaskExecutorRule解决了它。

你需要将以下依赖项添加到你的app build.gradle文件中:

testImplementation 'androidx.arch.core:core-testing:2.1.0'

在您的测试中包含以下规则:

// Executes tasks in the Architecture Components in the same thread
@get:Rule
var instantTaskExecutorRule = InstantTaskExecutorRule()

可能是这样 + RxJava立即调度器。但我不能确定,因为我完全放弃了Robolectric,只使用单元测试,但遵循这些规则。 - Syntey
我在Google的iosched项目中找到了这个修复:https://github.com/google/iosched/blob/main/mobile/src/test/java/com/google/samples/apps/iosched/ui/LaunchViewModelTest.kt。 - Zakaria Boukaddouss

0
问题在于运行UI测试的线程在没有任何错误的情况下结束,而assert却在另一个线程中运行。
您可以更改测试方法,使其保持线程,直到运行observeOnce的线程释放它:
@Test
fun givenBusinessMock_whenVerticalCollection_thenBusinessVerticalWith2Items() {

    val syncObject = Object()

    val activityScenario = ActivityScenario.launch(BusinessCollectionVerticalActivity::class.java)
    activityScenario.onActivity {
        activity = it
    }

    viewModel = ViewModelProviders.of(activity)[BusinessCollectionViewModel::class.java]

    viewModel.data.observeOnce {
        assert(it.second.size == 2)

        synchronized (syncObject) {
            syncObject.notify()
        }
    }

    synchronized (syncObject) {
        syncObject.wait()
    }
}

如果你想要更好的方法,可以在你的数据中使用observeForever并检查其值:
var observer: Observer<?> // replace ? by your type
viewModel.data.observeForever(observer)
assert(viewModel.data.value.second.size == 2)

更多信息请参见文章


第一种解决方案不起作用,第二种只有部分有效。当我运行测试时,它会失败并提示断言失败,因为viewModel.data.value为空。调试时,viewModel.data.value.second.size为2,所以这是正确的,但是当我更改断言条件以使其失败时,就会出现我在第一篇帖子中提到的与主循环器相关的异常。你有什么想法吗?谢谢。 - Syntey
你在第二种方法中放置了assert吗?你能否更新问题并附上代码? - Gustavo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接