You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Kotlin ViewModel单元测试始终通过问题求助

问题分析与解决方案

核心问题根源

  1. 测试逻辑完全绕过ViewModel:你在测试中直接调用repository.getAllGames(),这导致测试根本没有验证ViewModel的业务逻辑——ViewModel本应根据网络状态自动触发数据获取,而非手动调用Repository方法。
  2. 硬编码延迟不可靠:delay(500L)无法保证协程任务实际执行完成,容易出现测试通过但逻辑未触发的假阳性结果。
  3. FakeNetworkConnectivityObserver可能存在实现缺陷:如果该类未用StateFlow发射状态变化,ViewModel的collect逻辑无法接收状态更新,导致数据获取流程不触发。

修复步骤

1. 修正FakeNetworkConnectivityObserver实现

确保它用StateFlow正确发射网络状态变化:

class FakeNetworkConnectivityObserver : ConnectivityObserver {
    private val _status = MutableStateFlow(Status.Unavailable)
    override fun observe(): StateFlow<Status> = _status

    fun setStatus(status: Status) {
        _status.value = status
    }
}

2. 重构测试代码

移除直接调用Repository的逻辑,改用advanceUntilIdle()等待协程任务完成,确保测试验证ViewModel的实际行为:

@ExperimentalCoroutinesApi
class AllGamesViewModelTest{

    @get:Rule
    var instantTaskExecutorRule = InstantTaskExecutorRule()

    @get:Rule
    var mainCoroutineRule = MainCoroutineRule()

    private lateinit var connectivityObserver: FakeNetworkConnectivityObserver
    private lateinit var viewModel: AllGamesViewModel
    private lateinit var getAllGamesUseCase: GetAllGamesUseCase
    private lateinit var getAllGamesFromLocalUseCase: GetAllGamesFromLocalUseCase
    private lateinit var getGamesByGenreUseCase: GetGamesByGenreUseCase
    private lateinit var repository: FakeGameRepository
    private val games = mutableListOf<Game>()

    @Before
    fun setup(){
        repository = FakeGameRepository()
        connectivityObserver = FakeNetworkConnectivityObserver()
        getAllGamesUseCase = GetAllGamesUseCase(repository)
        getAllGamesFromLocalUseCase = GetAllGamesFromLocalUseCase(repository)
        getGamesByGenreUseCase = GetGamesByGenreUseCase(repository)
        viewModel = AllGamesViewModel(
            getAllGamesUseCase,
            getGamesByGenreUseCase,
            getAllGamesFromLocalUseCase,
            connectivityObserver
        )

        for(i in 0..10){
            val game = Game(
                developer = "developer $i",
                freeToGameProfileUrl = "freeUrl $i",
                gameUrl = "gameUrl $i",
                genre = "genre $i",
                id = i,
                platform = "plat $i",
                publisher = "publish $i",
                releaseDate = "release $i",
                shortDescription = "short $i",
                thumbnail = "thumb $i",
                title = "title $i"
            )
            games.add(game)
        }
        repository.insertGame(games)
    }

    @Test
    fun `get all games when network is available`() = runTest {
        connectivityObserver.setStatus(Status.Available)
        advanceUntilIdle()
        
        assertThat(viewModel.status.value).isEqualTo(Status.Available)
        assertThat(viewModel.state.value.allGames).containsExactlyElementsIn(games)
        assertThat(viewModel.state.value.isLoading).isFalse()
    }

    @Test
    fun `get all games from local when network is unavailable`() = runTest {
        connectivityObserver.setStatus(Status.Unavailable)
        advanceUntilIdle()
        
        assertThat(viewModel.status.value).isEqualTo(Status.Unavailable)
        assertThat(viewModel.state.value.allGames).containsExactlyElementsIn(games)
        assertThat(viewModel.state.value.isLoading).isFalse()
    }
}

3. 优化ViewModel冗余代码

移除不必要的withContext(Dispatchers.Main)——viewModelScope默认绑定Main调度器,无需手动切换:

fun getAllGames() {
    viewModelScope.launch {
        getGame().collect { result ->
            handleResult(result)
        }
    }
}

fun getAllGamesFromLocal(){
    viewModelScope.launch {
        getAllGamesFromLocalUseCase().collect{ result ->
            handleResult(result)
        }
    }
}

fun getTaggedGames(genre: String) {
    viewModelScope.launch {
        getGamesByGenre(genre).collect { result ->
            handleResult(result)
        }
    }
}

内容的提问来源于stack exchange,提问作者Hooman Hooshyar

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.04 12:47:23