共享流程永远不会完成。在共享流上对 Flow.collect 的调用永远不会正常完成,由 Flow.launchIn 函数启动的协程也不会正常完成。
所以调用 toList 将永远挂起,因为流程永远不会到达它说“好的,这就是所有元素”的终点,并且 toList 需要返回最终值。由于 StateFlow 一次只包含一个元素,而且您不会在一段时间内收集,因此您可能只需要 take(1).toList()。
或者,如果您不想要包装列表,则使用 first(),您似乎不需要包装列表 - StateFlow 中的每个元素都是一个 List<ClickRow>,这也是 clickRowRepository.fetchClickRows() 返回的内容。所以 expectedValue 是 List<ClickRow>,而 actualValue 是 List<List<ClickRow>> - 所以它们无论如何都不匹配!
编辑您的更新(使用first())有几个问题。
首先,您的 ViewModel 中的 clickRowsFlow StateFlow仅有的当您调用 fetchAndInitialiseClickRows() 时更新,因为这是获取值并将其设置在 StateFlow 上的内容。您没有在第二个示例中调用它,因此它不会更新。
其次,StateFlow 将经历两个状态值,对吗?第一个是初始空列表,第二个是您从 repo 返回的行内容。所以当你访问那个StateFlow时,它要么需要后更新已经发生,或者(更好)你需要忽略第一的声明并只返回第二一:
val actualValue = clickRowViewModel.clickRowsFlow
.drop(1) // ignore the initial state
.first() // then take the first result after that
// start the update -after- setting up the flow collection,
// so there's no race condition to worry about
clickRowsViewModel.fetchAndInitialiseClickRows()
这样,您订阅 StateFlow 并立即获得(和删除)初始状态。然后当更新发生时,它应该将另一个值推送给订阅者,订阅者将 first 新值作为其最终结果。
但是还有另一个并发症 - 因为 fetchAndInitialiseClickRows() 开始它自己的协程并立即返回,这意味着获取和更新任务正在异步运行。在开始声明任何结果之前,您需要给它时间来完成。
一种选择是启动协同程序,然后阻塞等待结果显示:
// start the update
clickRowsViewModel.fetchAndInitialiseClickRows()
// run the collection as a blocking operation, which completes when you get
// that second result
val actualValue = clickRowViewModel.clickRowsFlow
.drop(1)
.first()
这有效只要fetchAndInitialiseClickRows 没有立即完成.上面那个消费链需要至少要生产两个项目当它被订阅时- 如果它永远无法看到初始状态,它将挂起等待永远不会出现的第二个(实际上是第三个)值。这引入了竞争条件,即使它“在实践中可能很好”,它仍然会使测试变得脆弱。
您的另一个选择是先订阅,使用协程以便可以继续执行,并且然后开始更新 - 这样订阅者可以看到初始状态,然后是稍后到达的更新:
// async is like launch, but it returns a `Deferred` that produces a result later
val actualValue = async {
clickRowViewModel.clickRowsFlow
.drop(1)
.first()
}
// now you can start the update
clickRowsViewModel.fetchAndInitialiseClickRows()
// then use `await` to block until the result is available
assertEquals(expected, actualValue.await())
你总是需要确保你处理等待协程,否则测试可能会提前完成(即你在结果出现之前进行断言)。就像在您的第一个示例中一样,您正在启动一个协程来填充您的列表,但不确保在检查列表的内容之前有时间完成。
在那种情况下,你必须做类似advanceUntilIdle() - have a look at this section on testing coroutines 的事情,它会告诉你一些等待他们的方法。这也可能适用于您使用 fetchAndInitialiseClickRows 启动的那个(因为它说它等待调度程序上的其他协程,而不是相同的范围)但我不太熟悉它,如果您可以查看它喜欢!