从单元测试的 Dispatcher 访问 ViewModel MutableState?

Accessing ViewModel MutableState from unit tests' Dispatcher?

提问人:Andrey Sedelnikov 提问时间:7/21/2022 最后编辑:Vishal VasaniAndrey Sedelnikov 更新时间:3/26/2023 访问量:453

问:

我在 ViewModel for Compose 中使用 MutableStates

var desiredDate: MutableState<Date?> = mutableStateOf(null)

此外,我还想接收模型内部状态的变化

    snapshotFlow { desiredDate.value }
            .onEach { updateConfirmationState() }
            .launchIn(viewModelScope)

这作为生产性代码工作得很好。现在我想测试更新一个状态会导致单元测试中另一个状态的更新。我正在使用经典的 MainCoroutineRule 来覆盖 Dispatchers.Main。

现在,每个 snapshotFlow 启动都发生在单独的工作线程中,并且由于 MutableState 未共享,因此所有值均为 null。

应该有一种方法可以以这种方式测试它,对吧?

人造人 科特林 kotlin-协程 可变

评论

0赞 Oleg Nestyuk 9/22/2022
你好!你找到任何解决方案了吗?

答:

0赞 Milan 3/26/2023 #1

刚开始测试我的撰写视图模型并遇到了这篇文章

snapshotFlow 依赖于快照,在应用新快照时会发出新值,而不仅仅是在值更改时发出。在生产代码中,Compose 在后台管理所有快照

因此,这是我测试此视图模型的方式:

class SettingsViewModel @Inject constructor(
    userRepository: UserRepository
) : ViewModel() {
    private val _user = userRepository.getMe()
    private var _selectedAvatar by mutableStateOf<String?>(null)
    val state = combine(
        _user,
        snapshotFlow { _selectedAvatar },
    ) { user, selectedAvatar ->
        SettingsUiState(selectedAvatar ?: user?.avatar)
    }.stateIn(
        scope = viewModelScope,
        started = SharingStarted.WhileSubscribed(2_000),
        initialValue = SettingsUiState()
    )

    fun updateAvatar(imageUrl: String?) {
        _selectedAvatar = imageUrl
    }
}

data class SettingsUiState(
    val avatar: Any? = null
}

而在实际测试中:

@OptIn(ExperimentalCoroutinesApi::class)
class SettingsViewModelTest {
    @get:Rule
    val mockkRule = MockKRule(this)

    @get:Rule
    val dispatcherRule = TestDispatcherRule()

    @MockK
    lateinit var userRepository: UserRepository

    lateinit var viewModel: SettingsViewModel

    @Test
    fun `state returns updated SettingsUiState when selectedAvatar updates`() = runTest {
        val selectedAvatar = "http://www.example.com/2.jpg"
        val user = User(1, "John", "http://www.example.com/1.jpg")

        every { userRepository.getMe() } returns flowOf(user)

        viewModel = SettingsViewModel(userRepository)

        viewModel.state.test {
            assertEquals(SettingsUiState(user.avatar), awaitItem())

            withMutableSnapshot { viewModel.updateAvatar(selectedAvatar) }
            assertEquals(SettingsUiState(selectedAvatar), awaitItem())
        }
    }
}

请注意,我在 Turbine 内部调用,其中包含有关如何测试共享流的参考updateAvatar(..).test

另外,如果你的项目中还没有类似的东西,下面是一个示例TestDispatcherRule