首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >使用StateFlow和Coroutines的单元测试StateFlow

使用StateFlow和Coroutines的单元测试StateFlow
EN

Stack Overflow用户
提问于 2022-03-09 08:05:45
回答 2查看 2.2K关注 0票数 4
代码语言:javascript
复制
Kotlin 1.4.21

我有一个非常简单的ViewModel,它使用coroutine和stateFlow。但是,单元测试将失败,因为stateFlow似乎没有得到更新。

我认为这是因为测试将在stateFlow更新之前完成。

expected not to be empty

这是我的ViewModel正在测试中

代码语言:javascript
复制
class TrendingSearchViewModel @Inject constructor(
    private val loadTrendingSearchUseCase: LoadTrendingSearchUseCase,
    private val coroutineDispatcher: CoroutineDispatcherProvider
) : ViewModel() {

    private val trendingSearchMutableStateFlow = MutableStateFlow<List<String>>(emptyList())
    val trendingSearchStateFlow = trendingSearchMutableStateFlow.asStateFlow()

    fun getTrendingSearch() {
        viewModelScope.launch(coroutineDispatcher.io()) {
            try {
                trendingSearchMutableStateFlow.value = loadTrendingSearchUseCase.execute()
            } catch (exception: Exception) {
                Timber.e(exception, "trending ${exception.localizedMessage}")
            }
        }
    }
}

这是我的真正的考试课,我试过不同的方法让它开始工作。

代码语言:javascript
复制
class TrendingSearchViewModelTest {
    private val loadTrendingSearchUseCase: LoadTrendingSearchUseCase = mock()
    private val coroutineDispatcherProvider = CoroutineDispatcherProviderImp()
    private lateinit var trendingSearchViewModel: TrendingSearchViewModel

    @Before
    fun setUp() {
        trendingSearchViewModel = TrendingSearchViewModel(
            loadTrendingSearchUseCase,
            coroutineDispatcherProvider
        )
    }

    @Test
    fun `should get trending search suggestions`() {
        runBlocking {
            // Arrange
            val trending1 = UUID.randomUUID().toString()
            val trending2 = UUID.randomUUID().toString()
            val trending3 = UUID.randomUUID().toString()

            whenever(loadTrendingSearchUseCase.execute()).thenReturn(listOf(trending1, trending2, trending3))

            val job = launch {
                trendingSearchViewModel.trendingSearchStateFlow.value
            }

            // Act
            trendingSearchViewModel.getTrendingSearch()

            // Assert
            val result = trendingSearchViewModel.trendingSearchStateFlow.value
            assertThat(result).isNotEmpty()

            job.cancel()
        }
    }
}

这是我在测试中嘲笑的用法:

代码语言:javascript
复制
class LoadTrendingSearchUseCaseImp @Inject constructor(
    private val searchCriteriaProvider: SearchCriteriaProvider,
    private val coroutineDispatcherProvider: CoroutineDispatcherProvider
) : LoadTrendingSearchUseCase {

    override suspend fun execute(): List<String> {
        return withContext(coroutineDispatcherProvider.io()) {
            searchCriteriaProvider.provideTrendingSearch().trendingSearches
        }
    }
}

万一需要的话,这就是我的界面:

代码语言:javascript
复制
interface CoroutineDispatcherProvider {
    fun io(): CoroutineDispatcher = Dispatchers.IO
    fun default(): CoroutineDispatcher = Dispatchers.Default
    fun main(): CoroutineDispatcher = Dispatchers.Main
    fun immediate(): CoroutineDispatcher = Dispatchers.Main.immediate
    fun unconfined(): CoroutineDispatcher = Dispatchers.Unconfined
}

class CoroutineDispatcherProviderImp @Inject constructor() : CoroutineDispatcherProvider
EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2022-03-09 15:54:10

这就是对我有用的东西:

代码语言:javascript
复制
@Test
fun `should get trending search suggestions`() {
    runBlockingTest {
        // Arrange
        val trending1 = UUID.randomUUID().toString()
        val trending2 = UUID.randomUUID().toString()
        val trending3 = UUID.randomUUID().toString()
        val listOfTrending = listOf(trending1, trending2, trending3)

        whenever(loadTrendingSearchUseCase.execute()).thenReturn(listOfTrending)

        /* List to collect the results */
        val listOfEmittedResult = mutableListOf<List<String>>()
        val job = launch {
            trendingSearchViewModel.trendingSearchStateFlow.toList(listOfEmittedResult)
        }

        // Act
        trendingSearchViewModel.getTrendingSearch()

        // Assert
        assertThat(listOfEmittedResult).isNotEmpty()
        verify(loadTrendingSearchUseCase).execute()

        job.cancel()
    }
}
票数 0
EN

Stack Overflow用户

发布于 2022-03-09 08:42:44

我认为,当您需要更复杂的场景时,这个由杰克·沃顿( Jack )编写的库https://github.com/cashapp/turbine将会有很大的帮助。

我认为正在发生的是,在片段中调用.collect { },这确保了流的启动。检查终端操作符定义:流上的终端操作符是挂起启动流的集合的函数https://kotlinlang.org/docs/flow.html#terminal-flow-operators

对于sharedFlow来说则不是这样,它可能被配置为急切地启动。

所以为了解决你的问题,你可以打电话给

代码语言:javascript
复制
val job = launch {
    trendingSearchViewModel.trendingSearchStateFlow.collect()
}
票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/71406113

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档