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

Jetpack Compose ViewModel测试报错:cachedIn参数非空却为null

ViewModel测试中NullPointerException问题排查

报错信息

Parameter specified as non-null is null: method androidx.paging.CachedPagingDataKt.cachedIn, parameter <this>
java.lang.NullPointerException: Parameter specified as non-null is null: method androidx.paging.CachedPagingDataKt.cachedIn, parameter <this>

测试类代码

@ExperimentalCoroutinesApi
@RunWith(MockitoJUnitRunner::class)
class MoviePopularViewModelTest {

    @Mock
    lateinit var getPopularMoviesUseCase: GetPopularMoviesUseCase

    private lateinit var moviePopularViewModel: MoviePopularViewModel

    private val pagingDataMovies = PagingData.from(
        listOf(
            Movie(
                id = 1,
                title = "Avengers",
                voteAverage = 7.1,
                imageUrl = "Url"
            ),
            Movie(
                id = 2,
                title = "Iron Men",
                voteAverage = 7.9,
                imageUrl = "Url"
            )
        )
    )

    @Before
    fun setup() {
        moviePopularViewModel = MoviePopularViewModel(getPopularMoviesUseCase)
    }

     @Test
    fun `should validate the paging data object values when calling moviesPagingData`() = runTest {
       
     whenever(getPopularMoviesUseCase.invoke()).thenReturn(
            flowOf(pagingDataMovies)
        )

      val pagingData = pagingDataMovies
      val result = moviePopularViewModel.uiState.results.first()

     result.map { resultMovie ->
            pagingData.map { pagingData ->
                assertThat(pagingData.title).isEqualTo(resultMovie.title)
            }
        }

    }
}

ViewModel代码

@HiltViewModel
class MoviePopularViewModel @Inject constructor(
    private val getPopularMoviesUseCase: GetPopularMoviesUseCase
) : ViewModel() {

  var uiState by mutableStateOf(MoviePopularState())
        private set

  init {
        val movies = getPopularMoviesUseCase.invoke()
            .cachedIn(viewModelScope)
        uiState = uiState.copy(results = movies)
    }
}

问题原因与修复方案

核心问题:初始化顺序错误

ViewModel的init块在setup()方法创建ViewModel实例时会立即执行,但此时测试方法还未设置getPopularMoviesUseCase的mock返回值。Mockito默认给mock方法返回null,导致getPopularMoviesUseCase.invoke()返回空值,调用cachedIn时,作为扩展函数接收者的Flow为null,触发非空参数的NullPointerException。

测试逻辑的额外问题

  1. moviePopularViewModel.uiState.results.first()用法错误:results是Flow<PagingData<Movie>>,需通过collect收集数据,而非直接调用first()。
  2. 对PagingData的map操作不会直接执行,需通过收集行为触发数据加载。

修正步骤

  1. 调整mock时机:在创建ViewModel实例前,先设置好getPopularMoviesUseCase的mock返回值,确保init块执行时能拿到有效Flow。
  2. 修正测试逻辑:正确收集Flow并验证PagingData内容。

修正后的测试类代码示例

@ExperimentalCoroutinesApi
@RunWith(MockitoJUnitRunner::class)
class MoviePopularViewModelTest {

    @Mock
    lateinit var getPopularMoviesUseCase: GetPopularMoviesUseCase

    private lateinit var moviePopularViewModel: MoviePopularViewModel

    private val pagingDataMovies = PagingData.from(
        listOf(
            Movie(
                id = 1,
                title = "Avengers",
                voteAverage = 7.1,
                imageUrl = "Url"
            ),
            Movie(
                id = 2,
                title = "Iron Men",
                voteAverage = 7.9,
                imageUrl = "Url"
            )
        )
    )

    @Before
    fun setup() = runTest {
        // 创建ViewModel前先设置mock返回值
        whenever(getPopularMoviesUseCase.invoke()).thenReturn(flowOf(pagingDataMovies))
        moviePopularViewModel = MoviePopularViewModel(getPopularMoviesUseCase)
    }

    @Test
    fun `should validate the paging data object values when calling moviesPagingData`() = runTest {
        val collectedItems = mutableListOf<Movie>()
        // 收集Flow中的PagingData
        moviePopularViewModel.uiState.results.collect { pagingData ->
            pagingData.map { movie ->
                collectedItems.add(movie)
            }
        }
        // 验证收集到的数据是否符合预期
        assertThat(collectedItems.size).isEqualTo(2)
        assertThat(collectedItems[0].title).isEqualTo("Avengers")
        assertThat(collectedItems[1].title).isEqualTo("Iron Men")
    }
}

内容的提问来源于stack exchange,提问作者Daniel Lopes

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.16 15:57:05